【问题标题】:Ruby rcurry. How I can implement proc "right" currying?红宝石咖喱。我如何实现proc“正确”柯里化?
【发布时间】:2025-12-03 08:00:01
【问题描述】:

我正在开发一个新的 gem 来扩展 Ruby Core 类,类似于 Active Support Core ExtensionsPowerpack。这些天我正在研究Proc 课程,例如我添加了closure composition

但今天我想谈谈柯里化。这是curry 的 Ruby 标准库功能:

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“正确”柯里化以通过以下规范。这是几个月前报告的同一问题,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

【问题讨论】:

  • 你的问题是什么?
  • @sawa 我的问题是如何实现rcurry
  • 所以您希望有人为您编写代码,以便您可以将其作为 gem 发布?
  • 发布或不发布 gem 只是一个副产品。这里重要的是知识。我在解决问题时遇到了困难,或者我应该采取什么正确的方法。无论如何,如果这里的主要内容是 gem 的创建,我鼓励你创建一个支持 rcurry 的 gem 和 Jordan answer

标签: ruby lambda closures proc currying


【解决方案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 ]

所以我们在passed 中有[ 2 ],在args 中有[ 5 ]。我们希望mod2.call(5) 的返回值与modulus.call(2, 5) 的返回值相同——换句话说,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 将咖喱参数放在末尾而不是开头,所以信不信由你,我们只需要切换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 参数。这应该不会太难,所以我把它留给你练习。

【讨论】:

  • 感谢您的回答。不幸的是,仅适用于两个参数,但这是一个非常好的起点。例如,这失败了:b = ->(x, y, z) { (x||0) + (y||0) + (z||0) }expect(b.curry_b.call(1).call(2).call(3)).to eq(6)
最近更新 更多