Ruby 咖喱。我如何实现 proc "right" 柯里化?

Ruby rcurry. How I can implement proc "right" currying?

我正在新的 gem 中工作以扩展 Ruby 核心 类,类似于 Active Support Core Extensions or Powerpack. These days I'm working on the Proc class, for example I added closure composition

但是今天我想谈谈柯里化。这是 Ruby 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 "right" 柯里化以通过以下规范。这与几个月前报告的问题相同,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

我认为解决这个问题的方法是首先解决一个更简单的问题:我们如何实现我们自己的curry?

一件您可能知道也可能不知道的重要事情是 Proc#[]Proc#call 的别名,因此在上面的代码中 modulus.curry[2]modulus.curry.call(2) 相同.为清楚起见,我将在下面的代码中使用 .call

这是您规范中的第一段代码:

modulus = ->(mod, num) { num % mod }
mod2 = modulus.curry.call(2)

第二行告诉我们 modulus.curry return 是一个 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) 本身 return 是一个 Proc,所以现在我们的方法看起来像这样:

def curry_b
  proc do |*passed|
    proc do |*args|
      # ??
    end
  end
end

现在当我们调用mod2.call(5)时,最里面的块args的值将是[ 5 ]

所以我们在 passed 中有 [ 2 ],在 args 中有 [ 5 ]。我们希望 mod2.call(5) 的 return 值与 modulus.call(2, 5) 的 return 值相同——换句话说,modulus.call(*passed, *args)。在我们的方法中,modulusself,所以我们只需要这样做:

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 将 curried 参数放在末尾而不是开头,所以不管你信不信我们只需要切换 passedargs 左右:

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_brcurry 不同,不要使用 arity 参数。这应该不会太难,所以我会把它作为练习留给你。