Currying a proc with keyword arguments in Ruby

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 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.