我正在使用一个新的gem来扩展Ruby Core类,类似于Active Support Core Extensions或Powerpack。这些天我正在研究Proc
课程,例如我添加了closure composition。
但今天我想谈谈讨论。这是curry
:
describe "#curry" do
it "returns a curried proc" do
modulus = ->(mod, num) { num % mod }
mod2 = modulus.curry[2]
expect(mod2.call(5)).to eq(1)
end
end
我想添加一个新的rcurry
方法来支持proc“正确”currying以通过以下规范。这是几个月前报道的同一问题,Proc/Method#rcurry working like curry but in reverse order。
describe "#rcurry" do
it "returns a right curried proc" do
modulus = ->(mod, num) { num % mod }
mod2 = modulus.rcurry[2]
expect(mod2.call(5)).to eq(2)
end
end
答案 0 :(得分:1)
我认为解决这个问题的方法是先解决一个更简单的问题:我们如何实现自己的curry
?
您可能知道或不知道的一件重要事情是Proc#[]
是Proc#call
的别名,因此在上面的代码中modulus.curry[2]
与modulus.curry.call(2)
相同。为了清楚起见,我将在下面的代码中使用.call
。
以下是您的规范中的第一部分代码:
modulus = ->(mod, num) { num % mod }
mod2 = modulus.curry.call(2)
第二行告诉我们modulus.curry
返回一个Proc(因为我们在其上调用call
),所以我们知道我们的方法定义看起来有点像这样:
class Proc
def curry_b
proc do |*passed|
# ???
end
end
end
现在,当我们调用modulus.curry.call(2)
时,最里面的块中passed
的值将为[ 2 ]
(数组,因为我们使用了splat运算符)。
这是代码的下一部分:
mod2 = modulus.curry.call(2)
expect(mod2.call(5)).to eq(1)
在这里,我们看到modulus.curry.call(2)
本身返回一个Proc,所以现在我们的方法如下所示:
def curry_b
proc do |*passed|
proc do |*args|
# ??
end
end
end
现在,当我们致电mod2.call(5)
时,最里面的区块中args
的值将为[ 5 ]
。
我们[ 2 ]
中的passed
和[ 5 ]
中的args
。我们希望mod2.call(5)
的返回值与modulus.call(2, 5)
的返回值相同 - 换句话说,modulus.call(*passed, *args)
。在我们的方法中,modulus
为self
,因此我们必须这样做:
def curry_b
proc do |*passed|
proc do |*args|
self.call(*passed, *args)
end
end
end
现在我们可以缩短它并尝试一下:
class Proc
def curry_b
proc do |*passed|
proc {|*args| self[*passed, *args] }
end
end
end
mod2 = modulus.curry_b[2]
puts mod2.call(5)
# => 1
我们重新实现了curry
!那么,我们如何实施rcurry
?好吧,唯一的区别是rcurry
将讨论的论点放在最后而不是开头,所以不管你信不信,我们只需要转换passed
和args
:
class Proc
def rcurry
proc do |*passed|
proc {|*args| self[*args, *passed] }
end
end
end
modulus = ->(mod, num) { num % mod }
mod2 = modulus.rcurry[2]
p mod2.call(5)
# => 2
当然,这并不完美:与Proc#curry
curry_b
和rcurry
不同,不要采用 arity 参数。这不应该太难,所以我会把它作为锻炼给你。