你能为 Ruby 中的 map (& : method)语法提供参数吗?

您可能熟悉以下 Ruby 简写(a是一个数组) :

a.map(&:method)

例如,尝试在 irb 中执行以下操作:

>> a=[:a, 'a', 1, 1.0]
=> [:a, "a", 1, 1.0]
>> a.map(&:class)
=> [Symbol, String, Fixnum, Float]

语法 a.map(&:class)a.map {|x| x.class}的简写。

阅读更多关于“ Map (& : name)在 Ruby 中是什么意思?”语法的内容。

通过语法 &:class,为每个数组元素创建一个方法调用 class

我的问题是: 你能为方法调用提供参数吗? 如果可以,怎么提供?

例如,如何转换以下语法

a = [1,3,5,7,9]
a.map {|x| x + 2}

&:语法?

我并不是说 &:语法更好。 我只对在参数中使用 &:语法的机制感兴趣。

我假设您知道 +是 Integer 类上的一个方法:

>> a=1
=> 1
>> a+(1)
=> 2
>> a.send(:+, 1)
=> 2
50302 次浏览

正如你链接到的文章所证实的那样,a.map(&:class)不是 a.map {|x| x.class}的简写,而是 a.map(&:class.to_proc)的简写。

这意味着对 &操作符后面的任何内容调用 to_proc

所以你可以直接给它一个 Proc代替:

a.map(&(Proc.new {|x| x+2}))

我知道这很可能违背了你问题的目的,但是我看不出有其他的方法可以绕过这个问题——这并不是说你指定要调用哪个方法,你只是传递一些响应 to_proc的东西。

对于你的例子可以做 a.map(&2.method(:+))

Arup-iMac:$ pry
[1] pry(main)> a = [1,3,5,7,9]
=> [1, 3, 5, 7, 9]
[2] pry(main)> a.map(&2.method(:+))
=> [3, 5, 7, 9, 11]
[3] pry(main)>

下面是它的工作原理:-

[3] pry(main)> 2.method(:+)
=> #<Method: Fixnum#+>
[4] pry(main)> 2.method(:+).to_proc
=> #<Proc:0x000001030cb990 (lambda)>
[5] pry(main)> 2.method(:+).to_proc.call(1)
=> 3

2.method(:+)给出一个 Method对象。然后 &2.method(:+)上实际上是一个调用 #to_proc的方法,这使它成为一个 Proc对象。然后跟随 在 Ruby 中 & : 操作符叫什么?

简短的回答: 不。

按照@rkon 的回答,你也可以这样做:

a = [1,3,5,7,9]
a.map &->(_) { _ + 2 } # => [3, 5, 7, 9, 11]

你可以像这样在 Symbol上创建一个简单的补丁:

class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end

这不仅能让你做到这一点:

a = [1,3,5,7,9]
a.map(&:+.with(2))
# => [3, 5, 7, 9, 11]

但也有很多很酷的东西,比如传递多个参数:

arr = ["abc", "babc", "great", "fruit"]
arr.map(&:center.with(20, '*'))
# => ["********abc*********", "********babc********", "*******great********", "*******fruit********"]
arr.map(&:[].with(1, 3))
# => ["bc", "abc", "rea", "rui"]
arr.map(&:[].with(/a(.*)/))
# => ["abc", "abc", "at", nil]
arr.map(&:[].with(/a(.*)/, 1))
# => ["bc", "bc", "t", nil]

甚至可以使用 inject,它向块传递两个参数:

%w(abecd ab cd).inject(&:gsub.with('cde'))
# => "cdeeecde"

或者像传递[速记]块 那样超级酷的东西,速记块:

[['0', '1'], ['2', '3']].map(&:map.with(&:to_i))
# => [[0, 1], [2, 3]]
[%w(a b), %w(c d)].map(&:inject.with(&:+))
# => ["ab", "cd"]
[(1..5), (6..10)].map(&:map.with(&:*.with(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]

以下是我与@ArupRakshit 的对话,进一步解释了这一点:
你能为 Ruby 中的 map (& : method)语法提供参数吗?


正如在 评论如下中建议的@amcaplan,如果将 with方法重命名为 call,则可以创建一个更短的语法。在这种情况下,ruby 为这个特殊的方法 .()提供了一个内置的快捷方式。

所以你可以像这样使用上面的代码:

class Symbol
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end


a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11]


[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]

下面是一个使用 优化的版本(它比全局猴子修补 Symbol的版本更简单) :

module AmpWithArguments


refine Symbol do
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end


end


using AmpWithArguments


a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11]


[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]

与自己修补核心类不同,使用 方面宝石的功能更简洁:

require 'facets'
a = [1,3,5,7,9]
a.map &:+.(2)

对于可枚举数还有另一个本机选项,在我看来,它只适用于两个参数。类 Enumerable有方法 with_object,然后返回另一个 Enumerable

因此,您可以调用 &操作符来获取一个方法,该方法使用每个项和对象作为参数。

例如:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+) # => [3, 5, 7, 9, 11]

如果你想要更多的争论,你应该重复这个过程,但是在我看来这很丑陋:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+).to_enum.with_object(5).map(&:+) # => [8, 10, 12, 14, 16]

我不确定 Symbol#with是否已经发布了,我把它简化了一些,它运行得很好:

class Symbol
def with(*args, &block)
lambda { |object| object.public_send(self, *args, &block) }
end
end

(也使用 public_send而不是 send来防止调用私有方法,而且 caller已经被 Ruby 使用了,所以这是令人困惑的)

如果所有方法都需要数组中的元素作为参数,那么这可能是最简单的方法:

def double(x)
x * 2
end


[1, 2, 3].map(&method(:double))


=> [2, 4, 6]

我很惊讶没有人提到使用 curry,它从 Ruby 2.2.9开始就已经在 Ruby 中使用了。以下是如何按照 OP 希望的方式使用标准 Ruby 库:

[1,3,5,7,9].map(&:+.to_proc.curry(2).call(11))
# => [12, 14, 16, 18, 20]

但是,您需要为 curry提供一个与呼叫匹配的基础设施。这是因为解释器还不知道 +方法引用的是哪个对象。这也意味着只有当 map中的所有对象具有相同的属性时才能使用它。但是如果你试图这样使用它,这可能不是一个问题。