问题
I'm working in a new gem to extend the Ruby Core classes, something similar to Active Support Core Extensions or Powerpack. These days I'm working on the Proc
class, for example I added closure composition.
But today I like to talk about currying. This is the Ruby standard library functionality for 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
I like to add a new rcurry
method to support proc "right" currying to pass the following spec. It's the same issue reported a few months ago, 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
回答1:
I think the way to solve this is to first solve an easier problem: How do we implement our owncurry
?
One important thing you may or may not know is that Proc#[]
is an alias for Proc#call
, so in the above code modulus.curry[2]
is the same as modulus.curry.call(2)
. Just for clarity I'm going to use .call
in the code below.
Here's the first bit of code from your spec:
modulus = ->(mod, num) { num % mod }
mod2 = modulus.curry.call(2)
The second line tells us that modulus.curry
returns a Proc (because we invoke call
on it), so we know our method definition will look a bit like this:
class Proc
def curry_b
proc do |*passed|
# ???
end
end
end
Now when we call modulus.curry.call(2)
, the value of passed
in the innermost block will be [ 2 ]
(an array because we used the splat operator).
Here's the next part of your code:
mod2 = modulus.curry.call(2)
expect(mod2.call(5)).to eq(1)
Here we see that modulus.curry.call(2)
itself returns a Proc, so now our method looks like this:
def curry_b
proc do |*passed|
proc do |*args|
# ??
end
end
end
Now when we call mod2.call(5)
, the value of args
in the innermost block will be [ 5 ]
.
So we have [ 2 ]
in passed
and [ 5 ]
in args
. We want the return value of mod2.call(5)
to be the same as the return value of modulus.call(2, 5)
—in other words, modulus.call(*passed, *args)
. In our method , modulus
is self
, so we just have to do this:
def curry_b
proc do |*passed|
proc do |*args|
self.call(*passed, *args)
end
end
end
Now we can shorten it a bit and try it out:
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
We've reimplemented curry
! How, then, do we implement rcurry
? Well, the only difference is that rcurry
puts the curried arguments at the end instead of the beginning, so believe it or not we just have to switch passed
and args
around:
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
Of course, this isn't perfect: Unlike Proc#curry
curry_b
and rcurry
don't take an arity argument. That shouldn't be too tough, so I'll leave it as an exercise for you.
来源:https://stackoverflow.com/questions/34079551/ruby-rcurry-how-i-can-implement-proc-right-currying