在 Ruby 中使用关键字参数柯里化 proc
Currying a proc with keyword arguments in Ruby
假设我有一个通用的 Proc
、Lambda
或 method
,它带有一个可选的第二个参数:
pow = -> (base, exp: 2) { base**exp }
现在我想柯里化这个函数,给它一个 3
的 exp
。
cube = pow.curry.call(exp: 3)
由于关键字参数和新的散列语法,此处存在歧义,其中 Ruby 将 exp: 3
解释为作为第一个参数 base
传递的散列。这导致函数立即被调用,当 #**
被发送到散列时呈现 NoMethodError
。
为第一个参数设置默认值同样会导致函数在柯里化时立即被调用,如果我将第一个参数标记为必需,而不提供默认值:
pow = -> (base:, exp: 2) { base**exp }
当我尝试柯里化 Proc
.
时,解释器会抱怨我缺少参数 base
如何用第二个参数柯里化一个函数?
curry
不适用于关键字参数。柯里化函数一次获取一个参数,这在概念上与 "any order is fine" 关键字参数不兼容。
curry
必须知道确切的数量。如果你只是调用不带参数的 curry
,它将忽略任何可选参数(在 pow = -> (base, exp=2) { base**exp }
的情况下,与 curry(1)
相同)。使用 curry(2)
强制这两个参数。 curried 函数不知道后面有一个可选参数,并读取 future 以确定它是否应该执行或 return curried continuation。
我不认为你可以用 Proc.curry
做到这一点,但总有一种普通的方式
cube = -> (base) {pow.(base, exp: 3)}
您还可以创建工厂函数
pow_factory = -> (exp) {-> (base) {pow.(base, exp: exp)}}
cube = pow_factory.(3)
您可以构建自己的 keyword-flavored 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}
上面的示例仅限于关键字参数,但您当然可以扩展它以同时支持关键字参数和位置参数。
根据最后一条评论并根据他的代码段扩展 :
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}
假设我有一个通用的 Proc
、Lambda
或 method
,它带有一个可选的第二个参数:
pow = -> (base, exp: 2) { base**exp }
现在我想柯里化这个函数,给它一个 3
的 exp
。
cube = pow.curry.call(exp: 3)
由于关键字参数和新的散列语法,此处存在歧义,其中 Ruby 将 exp: 3
解释为作为第一个参数 base
传递的散列。这导致函数立即被调用,当 #**
被发送到散列时呈现 NoMethodError
。
为第一个参数设置默认值同样会导致函数在柯里化时立即被调用,如果我将第一个参数标记为必需,而不提供默认值:
pow = -> (base:, exp: 2) { base**exp }
当我尝试柯里化 Proc
.
base
如何用第二个参数柯里化一个函数?
curry
不适用于关键字参数。柯里化函数一次获取一个参数,这在概念上与 "any order is fine" 关键字参数不兼容。curry
必须知道确切的数量。如果你只是调用不带参数的curry
,它将忽略任何可选参数(在pow = -> (base, exp=2) { base**exp }
的情况下,与curry(1)
相同)。使用curry(2)
强制这两个参数。 curried 函数不知道后面有一个可选参数,并读取 future 以确定它是否应该执行或 return curried continuation。
我不认为你可以用 Proc.curry
做到这一点,但总有一种普通的方式
cube = -> (base) {pow.(base, exp: 3)}
您还可以创建工厂函数
pow_factory = -> (exp) {-> (base) {pow.(base, exp: exp)}}
cube = pow_factory.(3)
您可以构建自己的 keyword-flavored 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}
上面的示例仅限于关键字参数,但您当然可以扩展它以同时支持关键字参数和位置参数。
根据最后一条评论并根据他的代码段扩展
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}