你能在 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}

&:语法?

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

我假设你知道+是一个整数类的方法。您可以在irb中尝试以下操作:

>> a=1
=> 1
>> a+(1)
=> 2
>> a.send(:+, 1)
=> 2

您可以像这样在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(&:方法)语法提供参数吗?


正如@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]] 

这是一个使用Refinements的版本(这比全局猴子补丁Symbol更不hacky):

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]] 

对于您的示例可以做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对象。

正如您链接到的帖子所证实的那样,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的东西。

对于可枚举对象,还有另一个本地选项,在我看来,它只适用于两个参数。类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]

简短回答:No.

在@rkon的回答之后,你也可以这样做:

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

如果你的方法只需要数组中的一个元素作为参数,这可能是最简单的方法:

def double(x)
  x * 2
end
[1, 2, 3].map(&method(:double))
=> [2, 4, 6]

与其自己修补核心类,不如使用Facets宝石的功能更简洁:

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

我很惊讶没有人提到使用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中的所有对象具有相同的密度时使用此方法。但如果你想这样使用它,这可能不是问题

我不确定Symbol#with已经发布,我简化了它相当多,它工作得很好:

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

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

最新更新