Looking for a curry package

I have a lot of functions that I would like to add an extra method to with the code

f(; kwargs...) = (args...; kw...) -> f(args...; kwargs..., kw...)

where f is the function which takes at least one positional argument and one keyword argument. Is there a package that can do this?

1 Like

I made a macro that more or less solves my needs:

macro flexkw(f, g=f)
    :($f(; kwargs...) = (args...; kw...) -> $g(args...; kwargs..., kw...))
end

This allows me to just run

@flexkw f

to add the new method to the function f. If a package that can do this already exists, then I would still be interested in hearing about it.

1 Like

Should humour be allowed here, you might want to consider this package.

10 Likes

Rather than a macro that defines a function that itself returns an anonymous function that calls the method you want, does it fit in your use case to simply define a method with a different name?

f(a, b; negative=false, exponent=2) = negative ? (a - b)^exponent : (a + b)^exponent
f_negative(args...; kwargs...) = f(args...; negative=true, kwargs...)

@assert f(1, 2; exponent=3) == 27
@assert f(1, 2; negative=true, exponent=3) == -1
@assert f_negative(1, 2; exponent=3) == -1

Thanks for the reply; the anonymous function aspect is really important for me because I am using it as input to another function and want to try out several different combinations of keyword values.

e.g. if @flexkw f was run immediately after definition of the f that you define, then

a = sort(rand(55); lt=f(; negative=true, exponent=5))
b = sort(rand(55); lt=f(; exponent=4))
c = sort(rand(55); lt=f(; negative=true, exponent=8))

would be legal and (in my opinion) easier to read than

a = sort(rand(55); lt=(x,y)->f(x,y; negative=true, exponent=5))
b = sort(rand(55); lt=(x,y)->f(x,y; exponent=4))
c = sort(rand(55); lt=(x,y)->f(x,y; negative=true, exponent=8))

For 1 argument functions, this also makes the |> syntax neater:

g(x; a=11, b=7) = a*x + b
@flexkw g
rand(88) |> sort |> g(; a=5, b=8)
# rather than
rand(88) |> sort |> x->g(x; a=5, b=8)

PartialFunctions.jl can produce anonymous functions, but I prefer the solution that I have shown in this post. It is also the solution used by some functions in Base (e.g. contains).


@rafael.guerra : I like the humour :grinning_face_with_smiling_eyes:

2 Likes

Would the following be relevant?

Then your examples would be of the form

a = sort(rand(55); lt = FixKw(f; negative = true, exponent = 5))
2 Likes

Thanks for bringing this to my attention. For my specific situation I still prefer the @flexkw macro because it is shorter and easier for me personally to read. In a slightly different scenario (where I don’t own the function), e.g.

rand(88) |> x->sort(x; rev=true) #1

the FixKw version would be

rand(88) |> FixKw(sort; rev=true) #2

and would be up against

using PartialFunctions
rand(88) |> sort $ (; rev=true) #3
using PartialFuns
@underscores rand(88) |> sort(_; rev=true) #4

and

@flexkw flexkw_sort sort
rand(88) |> flexkw_sort(; rev=true) #5

Personally, I would rank these in the order 1,2,4,3,5 but with time I might end up preferring 2 over 1.

The main alternative is to write an eval loop like

for f in functions_to_curry
    @eval $f(; kwargs...) = (args...; kw...) -> $f(args...; kwargs..., kw...)
end

where functions_to_curry is a collection of symbols.

Compared to the macro approach it’s mostly a stylistic choice but it might be nicer if you have many functions and can construct functions_to_curry efficiently, e.g. extract all functions from a module.

1 Like

For the macro approach you could also consider writing the macro so it can work as a decorator on the function definition:

@flexkw function f(...)
...
end

(Pass through the original AST and append the extra method after extracting the function name from the AST.)

2 Likes

To make @flexkw work in all situations, one should probably wrap the variables f and g in esc. (Alternatively, one could wrap the whole expression in this case.)

1 Like

Thank you, yes this works better:

macro flexkw(f, g=f)
    esc(:($f(; kwargs...) = (args...; kw...) -> $g(args...; kwargs..., kw...)))
end

It can now be used in other modules and even inside other functions, e.g.:

function g()
    @flexkw flexkw_sort sort #flex_sort becomes just a local function
    rand(88) |> flexkw_sort(; rev=true)
end

The main issue with macro solutions here is that you have to have know you want to curry before you start typing, since macros look forward at the expression ahead of them for what to transform. I posted a feature request a few years ago for “backwards-looking” macros which would make it easier to do things like x %>% fun(z = 2) (or whatever syntax). But I don’t think it’s easily implemented currently.

1 Like