您可以在Ruby中为map(&:method)语法提供参数吗?


116

您可能熟悉以下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}

在“ Ruby中的map(&:name)意味着什么? ”中阅读有关此语法的更多信息。

通过语法&:class,您可以class对每个数组元素进行方法调用。

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

例如,如何转换以下语法

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

&:语法?

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

我假设您知道这+是Integer类上的方法。您可以在irb中尝试以下操作:

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

Answers:


139

您可以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" 

或将[shorthand]块传递速记块,这很酷:

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

5
太好了,希望这是Ruby核心的一部分!
Jikku Jose

6
@UriAgassi仅仅因为很多图书馆都这样做,并不是一个好习惯。尽管Symbol#with可能在核心库中不存在,并且定义该方法比重新定义现有方法的破坏性小,但它仍在更改(即覆盖)ruby库的核心类的实现。该练习应非常谨慎且谨慎地进行。\ n \ n请考虑从现有的类继承并修改新创建的类。通常可以获得可比较的结果,而不会改变核心红宝石类别的负面影响。
rudolph9

2
@ rudolph9 -我不敢苟同-的“覆盖”的定义是写一些东西,这意味着这是写的代码是不是不再可用,而这显然并非如此。关于您建议继承Symbol类的建议-这不是琐碎的(即使可能的话),因为它是这样的核心类(new例如,它没有方法),并且它的用法很麻烦(即使可能),这将使继承失败。增强的目的...如果您可以展示一个使用该实现并获得可比结果的实现-请分享!
Uri Agassi 2015年

3
我喜欢这种解决方案,但我认为您可以从中获得更多乐趣。而不是定义with方法,而是定义call。然后,您可以执行类似的操作,a.map(&:+.(2))因为object.()使用了该#call方法。而且当您使用它时,您可以编写有趣的内容,例如:+.(2).(3) #=> 5-有点LISPy,不是吗?
amcaplan

2
很想在核心中看到这一点-它是可以使用一些Sugar ala .map(&:foo)的常见模式
Stephen

48

为您的例子可以做到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中如何称呼&:运算符?


用法巧妙!那是否假设方法调用可以同时应用(即arr [element] .method(param)=== param.method(arr [element]))还是我感到困惑?
Kostas Rousis,2014年

@rkon我也没收到您的问题。但是,如果您看到Pry上面的输出,则可以了解它的工作原理。
奥雅纳(Arup Rakshit)2014年

5
@rkon不能同时使用。它在这种特殊情况下有效,因为它+是可交换的。

您如何提供多个参数?在这种情况下:a.map {| x | x.method(1,2,3)}
Zack Xu

1
这就是我的观点@sawa :)它的意义与+,但不会对其他方法或让我们说,如果你想用X来划分每个号码
科斯塔斯·Rousis

11

正如您链接到的帖子所确认的那样,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


1
另外请记住,您可以将proc设置为局部变量并将其传递给map。 my_proc = Proc.new{|i| i + 1}[1,2,3,4].map(&my_proc) => [2,3,4,5]
rudolph9

10

简短答案:不可以。

按照@rkon的回答,您也可以执行以下操作:

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

9
您是对的,但我认为还不&->(_){_ + 2}短于{|x| x + 2}
萨达

1
不是,那是@rkon在他的回答中所说的,所以我没有重复。
阿吉斯

2
@Agis虽然您的回答并不短,但看起来更好。
Jikku Jose

1
那是一个很棒的解决方案。
BenMorganIO 2015年

5

不用像在公认的答案中那样自己修补核心类,而是使用Facets gem的功能更短,更简洁

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

5

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

0

我不确定Symbol#with已经发布的内容,我对其进行了简化,并且效果很好:

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

(也用于public_send代替send防止调用私有方法,也caller已被ruby使用,因此令人困惑)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.