Say I have a generic Proc
, Lambda
or method
which takes an optional second argument:
pow = -> (base, exp: 2) { base
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.
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 call curry
with no arguments, it will ignore any optionals (in case of pow = -> (base, exp=2) { base**exp }
, same as curry(1)
). Use curry(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}
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)