Ruby Keyword Case Currying

Say I have a generic one Proc, Lambdaor methodone that takes an optional second argument:

pow = -> (base, exp: 2) { base**exp }

Now I want to perform this function by providing it with expof 3.

cube = pow.curry.call(exp: 3)

There is an ambiguity that arises because of keyword arguments and the new hash syntax, where Ruby interprets exp: 3as a hash passed as the first argument base. This causes the function to be called immediately, showing NoMethodErrorwhen it is #**sent to the hash.


Setting the default value for the first argument in the same way will cause the function to be called immediately upon currying, and if I mark the first argument if necessary, without specifying the default:

pow = -> (base:, exp: 2) { base**exp }

the interpreter will complain that I am missing an argument basewhen I try to curry Proc.


How can I execute a function with a second argument?

+6
source share
4 answers

You can create your own keyword curry method that collects keyword arguments until the required parameters are found. Sort of:

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 above example is limited only to keyword arguments, but you can expand it to support both keyword arguments and position arguments.

+8
source

I don’t think you can do it with help Proc.curry, but there is always a long way

cube = -> (base) {pow.(base, exp: 3)}

factory

pow_factory = -> (exp) {-> (base) {pow.(base, exp: exp)}}
cube = pow_factory.(3)
+4
  • curry . curried , " - ".
  • curry arity. curry , ( pow = -> (base, exp=2) { base**exp }, , curry(1)). curry(2), . curried , , , , .
+2

@Stefan :

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}
0

Source: https://habr.com/ru/post/1624905/


All Articles