In the documentation of Ruby's Proc.curry
method at https://ruby-doc.org/core-2.5.3/Proc.html#method-i-curry, it says:
curry -> a_proc
curry(arity) -> a_proc
Returns a curried proc. If the optional arity argument is given, it determines the number of arguments. A curried proc receives some arguments. If a sufficient number of arguments are supplied, it passes the supplied arguments to the original proc and returns the result. Otherwise, returns another curried proc that takes the rest of arguments.
I know arity
means the number of arguments of a function. However, I'm not quite clear how it works here. Can anyone help to explain, please? I've read the examples on the doc, but still confused.
Perhaps walking through a better example might help. Let us start with a simple proc:
p = proc { |a, b, c| "a=#{a.inspect}, b=#{b.inspect}, c=#{c.inspect}" }
p[1,2,3]
# "a=1, b=2, c=3"
If we call curry
without the arity
argument then it is pretty clear what's going on:
p.curry # evaluates to a proc
p.curry[1] # and a different proc
p.curry[1][2] # another different proc
p.curry[1][2][3] # "a=1, b=2, c=3"
p.curry[1,2] # yet another proc, hooray for procs!
p.curry[1,2][3] # "a=1, b=2, c=3"
p.curry[1,2,3] # "a=1, b=2, c=3"
So p.curry
gives us a succession of Proc
s by supplying values for the arguments until we have enough to evaluate the original Proc
. Now we start adding arity
values:
p.curry(1) # some proc
p.curry(1)[] # some other proc,
p.curry(1)[1] # "a=1, b=nil, c=nil"
p.curry(1)[1, 2] # "a=1, b=2, c=nil"
p.curry(1)[1, 2, 3] # "a=1, b=2, c=3"
p.curry(2) # a proc
p.curry(2)[] # another proc
p.curry(2)[1] # oh look, a proc, a lovely surprise
p.curry(2)[1][2] # "a=1, b=2, c=nil"
p.curry(2)[1, 2] # "a=1, b=2, c=nil"
p.curry(2)[1, 2, 3] # "a=1, b=2, c=3"
The arity
argument is setting the effective arity of the curried proc; don't bother looking at the real arity – p.curry.arity
, p.curry(1).arity
, ... – since it will always be -1
(i.e. variadic). The result is that p.curry(1)
is sort of like
proc { |a| p[a] }.curry # "change" p's arity to 1 then curry
and p.curry(2)
is sort of like:
proc { |a, b| p[a, b] }.curry # "change" p's arity to 2 then curry
etc. Keep in mind that just because a (non-lambda) proc has arity n
doesn't mean that you have to call it with n
arguments. A proc's arity is more of a suggestion than anything else.
Of course, if you try this chicanery with a lambda then everything goes sideways because lambdas care very much about their arity:
λ = ->(a, b, c) { "a=#{a.inspect}, b=#{b.inspect}, c=#{c.inspect}" }
λ[1] # ArgumentError (wrong number of arguments (given 1, expected 3))
λ.curry[1] # a lambda-proc
λ.curry[1][2][3] # "a=1, b=2, c=3"
λ.curry[1][2, 3] # "a=1, b=2, c=3"
λ.curry(1) # ArgumentError (wrong number of arguments (given 1, expected 3))
λ.curry(2) # ArgumentError (wrong number of arguments (given 2, expected 3))
λ.curry(3) # a lambda-proc that's just like λ.curry