Ruby rcurry. How I can implement proc “right” currying?

别等时光非礼了梦想. 提交于 2020-01-06 15:04:12

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!