假设我有一个通用的
Proc
、Lambda
或 method
,它带有一个可选的第二个参数:
pow = -> (base, exp: 2) { base**exp }
现在我想柯里化这个函数,给它一个
exp
的3
。
cube = pow.curry.call(exp: 3)
这里有一个歧义,由关键字参数和新的散列语法引起,其中 Ruby 将
exp: 3
解释为作为第一个参数 base
传递的散列。这导致函数立即被调用,当 NoMethodError
被发送到哈希时呈现 #**
。
为第一个参数设置默认值同样会导致函数在柯里化时立即被调用,如果我将第一个参数标记为必需,而不提供默认值:
pow = -> (base:, exp: 2) { base**exp }
口译员会抱怨说我缺少论点
base
当我试图咖喱Proc
.
如何用第二个参数柯里化一个函数?
您可以构建自己的关键字风格的 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}
上面的示例仅限于关键字参数,但您当然可以扩展它以同时支持关键字参数和位置参数。
我不认为你可以用
Proc.curry
做到这一点,但总有普通的方式
cube = -> (base) {pow.(base, exp: 3)}
你也可以创建一个工厂函数
pow_factory = -> (exp) {-> (base) {pow.(base, exp: exp)}}
cube = pow_factory.(3)
curry
不适用于关键字参数。柯里化函数一次获取一个参数,这在概念上与“任何顺序都可以”关键字参数不兼容。curry
必须知道确切的数量。如果你只是调用 curry
没有参数,它会忽略任何可选项(在 pow = -> (base, exp=2) { base**exp }
的情况下,与 curry(1)
相同)。使用 curry(2)
强制这两个参数。 curried 函数不知道后面有一个可选参数,并读取 future 以确定它是否应该执行或返回 curried continuation。在@Stefan answer这里扩展一个包含所有参数的解决方案:
# @param meth [Method, Proc, Lambda]
# @param on_missing [Symbol] what should do when there are missing required arguments
# - `:curry` -> it does a curry with the parameters received (default).
# - `:call` -> it calls anyway (will error).
# - `:safe` -> it sets to `nil` the missing required arguments and does a call.
# - `:return` -> it returns `nil` but doesn't call.
def curry(meth, on_missing = :curry)
-> (*args, **kargs, &block) do
params = meth.parameters
kparams = params.select { |type, _| (type == :keyreq) || (type == :key) }
aparams = params.select { |type, _| (type == :req) || (type == :opt) }
kreq_miss = kparams.select { |type, _| type == :keyreq }.map(&:last) - kargs.keys
req_miss = aparams.select { |type, _| type == :req }.count - args.count
req_miss = req_miss >= 0 ? req_miss : 0
ready = kreq_miss.empty? && req_miss == 0
if on_missing == :safe
unless params.find { |type, _| type == :keyrest }
(kargs.keys - kparams.map(&:last)).each { |name| kargs.delete(name) }
end
unless params.find { |type, _| type == :rest }
args = args[0..(aparams.count-1)] if args.count > aparams.count
end
unless ready
kreq_miss.each { |name| kargs[name] = nil }
args = args.dup.push(*Array.new(req_miss, nil))
ready = true
end
end
return meth.call(*args, **kargs, &block) if ready || on_missing == :call
return nil if on_missing == :return
# (default) on_missing == :curry
-> (*oth, **koth, &blk) do
curried = curry(meth, on_missing)
curried[*args, *oth, **kargs, **koth, &(blk || block)]
end
end
end
使用示例
def foo(a, b = :default, c:, d: :default)
args = { 'a' => a, 'b' => b, c: c, d: d }
yield(args) if block_given?
"foo called!"
end
bar = curry(method(:foo))["bar", d: "override"] {|r| pp r}
bar.call(c: "now")
# {"a"=>"bar", "b"=>:default, :c=>"now", :d=>"override"}
# => "foo called!"
curry(method(:foo), :safe)["bar", d: "override"] {|r| pp r}
# {"a"=>"bar", "b"=>:default, :c=>nil, :d=>"override"}
# => "foo called!"
curry(method(:foo), :return)["bar", d: "override"] {|r| pp r}
# => nil
curry(method(:foo), :call)["bar", d: "override"] {|r| pp r}
# ArgumentError (missing keyword: :c)
重载参数
curry(method(:foo), :safe).call("bar", "baz", "ignored", d: "override", z: "ignored") {|r| pp r}
# {"a"=>"bar", "b"=>"baz", :c=>nil, :d=>"override"}
# => "foo called!"
bar = curry(method(:foo))["bar", d: "override"]
bar = bar["override", z: "bad"] {|r| pp r}
bar.call(c: "call it")
# ArgumentError (unknown keyword: :z)