2

Ruby 1.9 内置的对柯里化的支持支持两种方法来处理带有任意数量参数的 proc:

my_proc = proc {|*x| x.max }

1)curry没有参数:my_proc.curry. 您将逗号分隔的参数传递给 curried proc,就像您传递给普通 proc 一样。如果参数的数量是任意的,这将无法实现正确的柯里化(如果某些参数不是 splat 则很有用)

2)curry带参数:my_proc.curry(n)这样,currying 被应用,就好像 proc 将带n参数一样。例如:

my_proc.curry(3).call(2).call(5).call(1) #=> 5

那么,如何使用任意数量的参数实现柯里化呢?这意味着,如果n没有给出?

我想到的一种方法是通过代理收集参数call,然后解析proc通过(如果使用/不使用参数method_missing以外的任何方法,请使用收集的参数调用),但我仍在寻找其他方法实现它。callcallproc

更新

正如 Andy H 所说,问题在于何时停止使用咖喱。call就我的目的而言,如果在调用除此之外的任何方法或call在没有参数的情况下调用任何方法时停止柯里化 / proc 评估,那将是可以的。

4

3 回答 3

7

内置curry方法对您不起作用。原因是它会产生一个 proc,只要它有足够的参数就可以进行评估。引用您链接到的文档:

一个 curried proc 接收一些参数。如果提供了足够数量的参数,它会将提供的参数传递给原始 proc 并返回结果。

这里要实现的关键点是零是 splat 参数的“足够数量的参数”。

f = ->(x, y, z, *args){ [x, y, z, args] } 
g = f.curry    # => a "curryable" proc
h = g.call(1)  # => another curryable proc
i = h.call(2)  # => another curryable proc
j = i.call(3)  # => [1, 2, 3, []]

正如您所展示的,这些“可curryable”过程可以一次传递一个参数,每次都返回一个新的可curryable proc,直到传递了足够的参数,此时它会进行评估。这也是为什么他们不能支持任意长度的参数列表的原因——它怎么知道什么时候停止柯里化并只进行评估?

如果您想要一种允许任意数量参数的不同柯里化方式,您可以定义自己的柯里化方法:

def my_curry(f, *curried_args)
  ->(*args) { f.call(*curried_args, *args) }
end

这是一个相当简单的实现,但可能符合您的目的。内置方法的主要区别在于它总是返回一个新的过程,即使已经传递了足够的参数,并且它不支持一次一个的“咖喱链”。

f = ->(x, y, z, *args) { [x, y, z, args] }

g = my_curry(f, 1)    # => a proc
g.call(2, 3)          # => [1, 2, 3, []] 
g.call(2, 3, 4, 5)    # => [1, 2, 3, [4, 5]]
g.call(2)             # => ArgumentError: wrong number of arguments (2 for 3)

h = my_curry(g, 2, 3) # => a proc
h.call                # => [1, 2, 3, []]
h.call(4, 5)          # => [1, 2, 3, [4, 5]]
于 2013-02-08T09:23:49.270 回答
2

我讨厌笨拙,但是忘记curry并写下来怎么样

my_proc = proc { |*x| x.max }
l = -> *x { my_proc.( 4, 6, 12, 1, *x ) }
l.call + 1 #=> 13

你知道,Matz 自己说这curry“只是给功能性孩子的玩具”。它的实现并不特别,它并不比你在上面看到的更快或不同。Curry 在 Ruby 中没那么有用……

对于那些不相信我说的话的人,这里是确切的引用和链接:

函数式编程儿童的复活节彩蛋

于 2013-02-08T16:28:49.553 回答
0

我实现了一个使用子类Basic Object作为包装器进行柯里化的解决方案:

class CurryWrap < BasicObject

  def initialize proc
    @proc = proc
    @args = []
    return self
  end

  def call *args
    if args.size == 1
      @args << args.first
      return self
    elsif args.empty?
      return @proc.call(*@args)
    else
      @proc.call(*@args).call(*args)
    end
  end

  def == other
    @proc.call(*@args) == other
  end

  def method_missing m, *args, &block
    @proc.call(*@args).send(m, *args, &block)
  end

end

有了这个,基本上可以做到以下几点:

my_proc = proc { |*x| x.max }

CurryWrap.new(my_proc).call(4).call(6).call(12).call(1) + 1 #=> 13

除了参数之外的任何东西call都可以解决这个过程。

我不知道这是否是最优雅的方法,所以我暂时不接受答案。

于 2013-02-08T11:08:09.887 回答