Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Currying a proc with keyword arguments in Ruby

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?

like image 217
Drenmi Avatar asked Jan 19 '16 06:01

Drenmi


2 Answers

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.

like image 152
Stefan Avatar answered Oct 15 '22 00:10

Stefan


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)
like image 45
John La Rooy Avatar answered Oct 15 '22 00:10

John La Rooy