0

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
Arturo Herrero
  • 12,772
  • 11
  • 42
  • 73
  • What is your question? – sawa Dec 04 '15 at 02:48
  • @sawa my question is how implement `rcurry`. – Arturo Herrero Dec 04 '15 at 10:32
  • So you want someone to write code for you so that you can publish that as a gem? – sawa Dec 04 '15 at 10:37
  • Publish or not publish a gem is just a byproduct. The important thing here is the knowledge. I got stuck solving the problem or what is the right approach that I should take. Anyway, if the main thing here is the creation of the gem, I encourage you to create a gem supporting `rcurry` with the [Jordan answer](http://stackoverflow.com/a/34082091/462015). – Arturo Herrero Dec 04 '15 at 10:50

1 Answers1

2

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.

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
  • Thank you for your answer. Unfortunately only works with two arguments but is a very good starting point. Eg, this fails: `b = ->(x, y, z) { (x||0) + (y||0) + (z||0) }` `expect(b.curry_b.call(1).call(2).call(3)).to eq(6)`. – Arturo Herrero Dec 06 '15 at 20:18