【问题标题】:Can you supply arguments to the map(&:method) syntax in Ruby?你可以为 Ruby 中的 map(&:method) 语法提供参数吗?
【发布时间】:2014-07-04 22:17:18
【问题描述】:

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

在“What does map(&:name) mean in Ruby?”中阅读有关此语法的更多信息。

通过语法&: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

【问题讨论】:

    标签: ruby


    【解决方案1】:

    您可以像这样在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 的对话,进一步解释:
    Can you supply arguments to the map(&:method) syntax in Ruby?


    正如@amcaplan 在comment below 中建议的那样,如果将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]] 
    

    【讨论】:

    • 太好了,希望这是 Ruby 核心的一部分!
    • @UriAgassi 仅仅因为很多图书馆都这样做并不能使它成为一个好习惯。虽然Symbol#with 可能不存在于核心库中,并且定义该方法比重新定义现有方法的破坏性更小,但它仍在更改(即覆盖)ruby 库的核心类的实现。练习应该非常谨慎和谨慎。 \n\n 请考虑从现有类继承并修改新创建的类。这通常可以获得可比较的结果,而不会产生更改核心 ruby​​ 类的负面影响。
    • @rudolph9 - 我不敢苟同 - “覆盖”的定义是写 over 的东西,这意味着写的代码不再可用,而这显然不是这样。关于继承 Symbol 类的建议 - 这不是微不足道的(如果可能的话),因为它是这样一个核心类(例如,它没有 new 方法),并且它的使用会很麻烦(如果可能的话) ),这将违背增强的目的......如果你能展示一个使用它的实现,并取得可比的结果 - 请分享!
    • 我喜欢这个解决方案,但我认为您可以从中获得更多乐趣。不要定义with 方法,而是定义call。然后你可以做像a.map(&:+.(2)) 这样的事情,因为object.() 使用#call 方法。当你在做的时候,你可以写一些有趣的东西,比如:+.(2).(3) #=> 5——感觉有点像 LISPy,不是吗?
    • 希望在核心中看到这一点 - 它是一种常见的模式,可以使用一些糖 ala .map(&:foo)
    【解决方案2】:

    你的例子可以做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 对象。然后&amp;,在2.method(:+) 上,实际上是调用#to_proc 方法,使其成为Proc 对象。然后关注What do you call the &: operator in Ruby?

    【讨论】:

    • 巧妙的用法!这是否假设方法调用可以以两种方式应用(即 arr[element].method(param) === param.method(arr[element]))还是我很困惑?
    • @rkon 我也没有得到你的问题。但是如果你看到上面的Pry 输出,你就可以得到它,它是如何工作的。
    • @rkon 这两种方式都不起作用。它适用于这种特殊情况,因为+ 是可交换的。
    • 如何提供多个参数?在这种情况下: a.map {|x| x.method(1,2,3)}
    • 这就是我的观点@sawa :) + 是有意义的,但对于另一种方法则没有意义,或者假设你想将每个数字除以 X。
    【解决方案3】:

    正如您链接到的帖子所证实的,a.map(&amp;:class) 不是 a.map {|x| x.class} 的简写,而是 a.map(&amp;:class.to_proc) 的简写。

    这意味着to_proc&amp; 运算符后面的任何内容上都会被调用。

    所以你可以直接给它Proc

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

    我知道这很可能违背了您的问题的目的,但我看不出有任何其他方式解决它 - 并不是您指定要调用哪个方法,您只需传递一些响应 to_proc 的东西。

    【讨论】:

    • 另外请记住,您可以将 procs 设置为局部变量并将它们传递给 map。 my_proc = Proc.new{|i| i + 1}, [1,2,3,4].map(&amp;my_proc) =&gt; [2,3,4,5]
    【解决方案4】:

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

    因此,您可以调用 &amp; 运算符,以将每个项目和对象作为参数的方法。

    例子:

    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]
    

    【讨论】:

    • 使用这种方法需要注意三点:1.) 它实际上比 a.map { |n| n + 2 } 更长(就字符而言),2.) 可以说,它的可读性较差,3.) 它是比直接在阵列上运行 map() 慢 5 倍(约 19 秒,而使用 10_000_000.times {} 进行基准测试时约 3.75 秒。话虽如此,我仍然赞成,因为它绝对是手头问题的创造性解决方案。:-)
    【解决方案5】:

    简短回答:不。

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

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

    【讨论】:

    • 你是对的,但我不认为&amp;-&gt;(_){_ + 2}{|x| x + 2} 短。
    • 不是,@rkon 在他的回答中是这么说的,所以我没有重复。
    • @Agis 虽然你的答案不短,但看起来更好。
    • 这是一个很棒的解决方案。
    【解决方案6】:

    不像接受的答案那样自己修补核心类,使用Facets gem的功能更短更简洁:

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

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

        我不确定Symbol#with 已经发布了,我简化了很多,效果很好:

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

        (也使用public_send 而不是send 来防止调用私有方法,而且caller 已经被ruby 使用,所以这很混乱)

        【讨论】:

          【解决方案9】:

          我很惊讶还没有人提到使用curry,它自 Ruby 2.2.9 以来一直在 Ruby 中。以下是使用标准 Ruby 库以 OP 想要的方式完成的方法:

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

          不过,您需要向curry 提供与调用匹配的参数。这是因为解释器还不知道+ 方法指的是哪个对象。这也意味着您只能在 map 中的所有对象都具有相同数量时使用它。但是,如果您尝试以这种方式使用它,这可能不是问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2021-06-07
            • 1970-01-01
            • 1970-01-01
            • 2010-12-15
            • 2019-04-04
            • 2016-12-20
            • 1970-01-01
            相关资源
            最近更新 更多