【问题标题】:Currying a proc with keyword arguments in Ruby在 Ruby 中使用关键字参数对 proc 进行柯里化
【发布时间】:2016-01-19 06:10:52
【问题描述】:

假设我有一个通用的 ProcLambdamethod,它接受一个可选的第二个参数:

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

现在我想 curry 这个函数,给它一个 exp3

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

这里有一个歧义,源于关键字参数和新的哈希语法,其中 Ruby 将 exp: 3 解释为作为第一个参数 base 传递的哈希。这导致该函数立即被调用,当#** 被发送到哈希时呈现一个NoMethodError


为第一个参数设置默认值同样会导致在currying时立即调用该函数,如果我将第一个参数标记为必需,而不提供默认值:

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

当我尝试对Proc 进行柯里化时,解释器会抱怨我缺少参数base


如何使用第二个参数对函数进行柯里化?

【问题讨论】:

标签: ruby functional-programming currying


【解决方案1】:

您可以构建自己的关键字风格的 curry 方法,该方法收集关键字参数,直到出现所需的参数。比如:

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}

上面的示例仅限于关键字参数,但您当然可以扩展它以同时支持关键字参数和位置参数。

【讨论】:

  • 非常感谢@stefan 的示例实现!我也是这么想的,但我打算把它命名为#scurry。 :-P
【解决方案2】:

我不认为你可以用 Proc.curry 做到这一点,但总有一种简单的方法

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

你也可以创建一个工厂函数

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

【讨论】:

  • 使用工厂函数的想法绝对有趣! :-)
【解决方案3】:
  • curry 不适用于关键字参数。柯里化函数一次获取一个参数,这在概念上与“任何顺序都可以”关键字参数不兼容。
  • curry 必须知道确切的数量。如果您只调用 curry 而不使用任何参数,它将忽略任何选项(在 pow = -&gt; (base, exp=2) { base**exp } 的情况下,与 curry(1) 相同)。使用curry(2) 强制这两个参数。柯里化函数无法知道后面有一个可选参数,并读取未来以确定它是否应该执行或返回柯里化延续。

【讨论】:

  • 好的。考虑第二个参数不是可选的。我如何(如果有的话)从第二个参数开始咖喱? :-)
  • AFAIK,再次不可能。 curry 将允许您按照它们在函数签名中的顺序提供参数。如果你想把x.(a, b) 转换成xc.(b).(a),你必须自己写:xc = -&gt; (b, a) { x.(a, b) }.curry,或者xc = -&gt; (b) { -&gt; (a) { x.(a, b) } }。否则,我不知道您将如何将所需的参数顺序传达给curry
【解决方案4】:

根据最后一条评论扩展@Stefan answer above并符合他的sn-p:

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}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-15
    • 2011-08-06
    • 1970-01-01
    • 2020-01-28
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多