Say I have a generic Proc
, Lambda
or method
which takes an optional second argument:
pow = -> (base, exp: 2) { base**exp }
Now I want to curry this function, giving it an exp
of 3
.
cube = pow.curry.call(exp: 3)
There's an ambiguity here, arising from the keyword arguments and the new hash syntax, where Ruby interprets exp: 3
as a hash being passed as the first argument, base
. This results in the function immediately being invoked, rendering a NoMethodError
when #**
is sent to the hash.
Setting a default value for the first argument will similarly result in the function being immediately invoked when currying, and if I mark the first argument as required, without providing a default:
pow = -> (base:, exp: 2) { base**exp }
the interpreter will complain that I'm missing argument base
when I attempt to curry the Proc
.
How can I curry a function with the second argument?
You could build your own keyword-flavored curry method that collects keyword arguments until the required parameters are present. Something like:
def kw_curry(method)
-> (**kw_args) {
required = method.parameters.select { |type, _| type == :keyreq }
if required.all? { |_, name| kw_args.has_key?(name) }
method.call(**kw_args)
else
-> (**other_kw_args) { kw_curry(method)[**kw_args, **other_kw_args] }
end
}
end
def foo(a:, b:, c: nil)
{ a: a, b: b, c: c }
end
proc = kw_curry(method(:foo))
proc[a: 1] #=> #<Proc:0x007f9a1c0891f8 (lambda)>
proc[b: 1] #=> #<Proc:0x007f9a1c088f28 (lambda)>
proc[a: 1, b: 2] #=> {:a=>1, :b=>2, :c=>nil}
proc[b: 2][a: 1] #=> {:a=>1, :b=>2, :c=>nil}
proc[a: 1, c: 3][b: 2] #=> {:a=>1, :b=>2, :c=>3}
The example above is limited to keyword arguments only, but you can certainly extend it to support both, keyword arguments and positional arguments.
I don't think you can do it with Proc.curry
, but there is always the longhand way
cube = -> (base) {pow.(base, exp: 3)}
You could also create a factory function
pow_factory = -> (exp) {-> (base) {pow.(base, exp: exp)}}
cube = pow_factory.(3)
curry
does not work with keyword arguments. A curried function is getting one parameter at a time, which is conceptually incompatible with "any order is fine" keyword arguments.curry
must know the exact arity. If you just callcurry
with no arguments, it will ignore any optionals (in case ofpow = -> (base, exp=2) { base**exp }
, same ascurry(1)
). Usecurry(2)
to force both parameters. A curried function can't know an optional parameter is following, and read the future to determine if it should execute or return a curried continuation.
Extending @Stefan answer above as per last comment and in line with his snippet:
def curry(method)
-> (*args, **kargs) {
required = method.parameters.select { |type, _| type == :req }
krequired = method.parameters.select { |type, _| type == :keyreq }
all_args = (required.length <= args.length)
all_keys = krequired.all? { |_, name| kargs.has_key?(name) }
if all_args && all_keys
final_args = (args + kargs.map {|k,v| {k => v} })
method.call(*final_args)
else
-> (*args_, **kargs_) { curry(method)[*args, *args_, **kargs, **kargs_] }
end
}
end
def foo(a1, b1, c1 = 5, a2:, b2:, c2: 50)
{ a1: a1, b1: b1, c1: c1, a2: a2, b2: b2, c2: c2}
end
puts foz = curry(method(:foo)) #=> #<Proc:0x0000000003a255f0@./training.rb:56 (lambda)>
puts bar = foz[6, a2: 60] #=> #<Proc:0x0000000003a255f0@./training.rb:56 (lambda)>
puts bar[1, b2: 10] #=> {:a1=>6, :b1=>1, :c1=>5, :a2=>60, :b2=>10, :c2=>50}
puts baz = bar[1] #=> #<Proc:0x0000000003a17540@./training.rb:64 (lambda)>
puts baz[10, b2: 30, c2: 40] #=> {:a1=>6, :b1=>1, :c1=>10, :a2=>60, :b2=>30, :c2=>40}
来源:https://stackoverflow.com/questions/34869659/currying-a-proc-with-keyword-arguments-in-ruby