【问题标题】:Symbol#to_proc with custom methods带有自定义方法的 Symbol#to_proc
【发布时间】:2015-02-07 22:46:15
【问题描述】:

我喜欢在 Ruby 中如何使用 Symbol#to_proc 将方法作为块传递:

[1.0, 2.0, 3.0].map(&:to_i)
#=> [1, 2, 3]

我还可以定义自己的 lambda times_two,并将其作为块传递:

times_two = ->(x) {x * 2}

[1, 2, 3].map(&times_two)
#=> [2, 4, 6]

虽然我似乎不能times_two 作为符号传递:

[1, 2, 3].map(&:times_two)
#=> ArgumentError: wrong number of arguments (0 for 1)

但是,当我尝试对某个方法执行相同操作时,我得到一个错误:

def times_three(x)
  x * 3
end

[1, 2, 3].map(&times_three)
#=> ArgumentError: wrong number of arguments (0 for 1)

[1, 2, 3].map(&:times_three)
#=> ArgumentError: wrong number of arguments (0 for 1)

我猜我不能这样做,因为times_three 是一种方法,而不是 Proc。

那么如何定义自定义方法,以便它们可以像上面第一个示例中的to_i 一样以Symbol#to_proc 的方式使用?

例如,我该怎么做?

[1, 2, 3].map(&:times_three)
#=> [3, 6, 9]

编辑: 我观看了下面发布的视频,显然您可以使用 method 方法接近 Symbol#to_proc:

def times_three(x)
  x * 3
end

t_three = method(:times_three)
[1, 2, 3].map(&t_three)
#=> [3, 6, 9]

但是,它并不完全是 Symbol#to_proc:

[1, 2, 3].map(&:t_three)
#=> NoMethodError: undefined method `t_three' for 1:FixNum

【问题讨论】:

    标签: ruby


    【解决方案1】:
    class Integer
      def times_three
        return self * 3
      end
    end
    

    现在,因为 times_three 现在是 Integer 类的一个方法,你可以做符号来处理...

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

    如果要访问不属于对象类但作用于对象的方法,则需要将对象作为参数传递给方法...

    def times_three(x)
      x * 3
    end
    
    [1, 2, 3].map{|i| times_three(i) }
    

    symbol to proc 需要使用对象作为接收者。

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

    等价于

    [1, 2, 3].map{|i| i.some_action}
    

    【讨论】:

    • 非常感谢史蒂夫的清晰解释。既然我知道如何做到这一点,那么使用这些方法对核心类进行修补以清理复杂或重复的块是否可以接受?
    • 嗯,是的,只要意图明确并且有好处,猴子补丁肯定没问题。本周我有一个 Rails 应用程序,其中强制输入字段的标签中有一个星号,例如'First Name *''City *' 和企业突然希望所有星号都是红色字体颜色。我本可以编写一个像 red_asterisk('First Name *') 这样的辅助方法,但是更容易对 String 类 'First Name *'.red_asterisk 进行修补,然后在 *' 上进行全局查找/替换,这样它就变成了 *'.red_asterisk
    • Steve,众所周知,return 是不需要的。但是,self 是。我假设 self 对于使用语法糖调用的方法,例如 *%,必须是显式的。你或其他人知道这是不是真的?
    • @CarySwoveland 我假设它是,因为我假设语法糖不被解析器使用,只是由我们简单的编码人员使用。 :) 在我的概念模型中 * 可能被解释为 self.*(arg) 或更可能是 self.send(:*, arg) 所以你需要 def *; result =0; arg.times{result += self};result;end 如果输出是有条件的接收器(3*5 与 7*5 不同),您需要使用self。如果你想输出无条件的,比如3*5 => "I dunno"; 7*5 => "I dunno",那么你就跳过self。任何读到这个的新手,不要在家里尝试这个!不适合猴子补丁*%+
    【解决方案2】:

    您必须在IntegerNumeric 上定义times_three

    Peter Cooper 解释的 Proc 符号:https://www.youtube.com/watch?v=aISNtCAZlMg

    【讨论】:

    • 不要链接到随机参考,您应该在此处写下简短的解释/复制它并将链接作为您的 anwser 的来源。
    • 感谢您的视频。但是它没有说明为什么我必须在IntegerNumeric 上定义times_three。它展示了在 Ruby 采用 Symbol#to_proc 之前,to_proc 是如何在一个类上实现的,尽管视频没有展示如何以这种方式定义自己的方法。
    • @garythegoat:它确实显示了Symbol#to_proc 的实现方式,即它在块的第一个参数上调用方法,将块的其他参数作为方法调用的参数传递。在您的情况下,块的第一个参数是 1 (在第一次迭代中),并且没有其他参数,所以 Symbol#to_proc 调用 1.times_three, ergo, times_three 需要在 @ 之一中定义987654337@ 的类,即FixnumIntegerNumericObjectKernelBasicObject。不幸的是,the docs are crap,……
    • ... 和 the source code of Symbol#to_proc in YARV 也没有多大帮助。值得庆幸的是,Rubinius 从未让人失望,源代码更具可读性,事实上,它的内容基本上只有一行:proc do |*args, &b| args.shift.send(self, *args, &b) end
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-08
    • 1970-01-01
    • 1970-01-01
    • 2011-09-23
    • 2021-06-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多