This is a pretty hefty proposal. I do think we need better ways to succinctly express partial evaluation of functions, but honestly to my eye, I don’t really like any of the proposed options here. I find nearly all of them pretty hard to read. Honestly, I think julia already has a gigantic amount of syntactic forms so my default position is one of being skeptical of adding more.
I think the main difficulty I have with your proposal is that one specifies that they want currying at the call-site, meaning that these special syntactic forms are required each time we want to call a curried function, which leads to a big bloat of arcane symbols all over the place.
An alternative approach is just to annotate a function definition (instead of call) to say that we want that function to have currying behaviour.
For instance, we could imagine creating a macro @curried
such that
@curried foo(x, y, z) = (x^2 + y^2)/(x^2 + y^2 +z^2)
will define extra methods such that foo(x)(y)(z)
is equivalent to foo(x, y, z)
.
The macro could be written as follows:
struct FullyCurried end
macro curried(fdef)
f = fdef.args[1].args[1]
fargs = fdef.args[1].args[2:end]
arity = length(fargs)
body = fdef.args[2]
err_str = "Too many arguments. Function $f only takes $arity arguments"
quote
begin
function $f(args...)
if length(args) < $arity
x -> $f((args..., x)...)
elseif length(args) == 3
$f(FullyCurried(), args...)
else
throw($err_str)
end
end
$f(::FullyCurried, $(fargs...)) = $body
end
end |> esc
end
With that, we can do
julia> @curried foo(x, y, z) = (x^2 + y^2)/(x^2 + y^2 +z^2)
foo (generic function with 2 methods)
julia> foo(1)(2)(3)
0.35714285714285715
julia> foo(1, 2, 3)
0.35714285714285715
and some benchmarking / looking at LLVM code confirms that there is no runtime overhead to the curried form versus the non-curried form. This approach requires no modifications to julia’s parser which I quite like.