当内省模块类时,“#map(&proc)”成语如何工作?

提出成语

我发现了一个有趣但无法解释的替代方案 。 代码显然适用于REPL。 例如:

module Foo class Bar def baz end end end Foo.constants.map(&Foo.method(:const_get)).grep(Class) => [Foo::Bar] 

但是,我并不完全理解这里使用的习语。 特别是,我不理解使用&Foo ,它似乎是某种闭包,或者#grep的这种特定调用如何对结果进行操作。

解析成语

到目前为止,我已经能够解析其中的一些部分,但我并没有真正看到它们如何组合在一起。 以下是我认为我对示例代码的理解。

  1. Foo.constants返回模块常量数组作为符号。

  2. method(:const_get)使用Object#方法执行方法查找并返回闭包。

  3. Foo.method(:const_get).call :Bar是一个闭包,它返回类中常量的限定路径。

  4. &Foo似乎是某种特殊的lambda 。 文档说:

    如果proc对象由&参数给出,&参数将保留技巧。

    我不确定我是否完全明白在这个特定背景下这意味着什么。 为什么选择Proc? 什么“诡计”,为什么这里有必要?

  5. grep(Class)正在对#map方法的值进行操作,但其function并不明显。

    • 为什么这个#map构造返回一个greppable Array而不是Enumerator?

       Foo.constants.map(&Foo.method(:const_get)).class => Array 
    • 如何为名为Class的类的grepping实际工作,为什么这里需要特定的构造?

       [Foo::Bar].grep Class => [Foo::Bar] 

问题,重申

我真的很想完全理解这个成语。 任何人都可以在这里填补空白,并解释这些碎片是如何组合在一起的吗?

&Foo.method(:const_get)Foo对象的方法const_get 。 这是另一个例子:

 m = 1.method(:+) #=> # m.call(1) #=> 2 (1..3).map(&m) #=> [2, 3, 4] 

所以最后这只是说Foo.constants.map { |c| Foo.const_get(c) }一种无关紧要的方式 Foo.constants.map { |c| Foo.const_get(c) }grep使用===来选择元素,因此它只会获得引用类的常量,而不是其他值。 这可以通过向Foo添加另一个常量来validation,例如Baz = 1 ,这将不会得到grep ped。

如果您还有其他问题,请将其添加为评论,我会尝试澄清它们。

你对这个成语的解析非常有用,但我会仔细阅读并尝试澄清你提到的任何问题。

Foo.constants

如前所述,这会返回一个模块常量名称数组作为符号。

2. Array#map

你显然知道这是做什么的,但我想把它包括在内以保证完整性。 Map接受一个块并调用阻塞,每个元素作为参数。 它返回这些块调用结果的Array

3. Object#method

同样如您所述,这会进行方法查找。 这很重要,因为Ruby中没有括号的方法是没有任何参数的方法的方法调用。

4. &

此运算符用于将事物转换为块。 我们需要这个,因为块不是Ruby中的第一类对象。 由于这种二级状态,我们无法创建独立的块,但我们可以将Procs转换为块(但只有当我们将它们传递给函数时)! &运算符是我们进行此转换的方式。 每当我们想要传递一个Proc对象就像它是一个块一样,我们可以在它前面加上&运算符并将它作为函数的最后一个参数传递给它。 但是&实际上可以转换不仅仅是Proc对象,它可以转换任何具有to_proc方法的东西!

在我们的例子中,我们有一个Method对象,它有一个to_proc方法。 Proc对象和Method对象之间的区别在于它们的上下文。 Method对象绑定到类实例,并且可以访问属于该类的变量。 Proc被绑定到创建它的上下文; 也就是说,它可以访问创建它的范围。 Method#to_proc捆绑Method#to_proc的上下文,以便生成的Proc可以访问相同的变量。 您可以在此处找到有关&运算符的更多信息。

5. grep(Class)

Enumerable#grep工作的方式是它为枚举中的所有x运行argument === x 。 在这种情况下, ===的参数的排序非常重要,因为它调用的是Class.===而不是Foo::Bar.=== 。 我们可以通过运行来看到这两者之间的区别:

  irb(main):043:0> Class === Foo::Bar => true irb(main):044:0> Foo::Bar === Class => false 

Module#===ClassMethodinheritance其===方法)当参数是Module一个实例或其后代之一(如Class !)时,它返回True ,它将过滤掉不属于ModuleClass类型的常量。 您可以在此处找到Module#===的文档。

首先要知道的是:

&在后面的对象上调用to_proc ,并使用作为方法块生成的proc。

现在,您必须深入了解to_proc方法在特定类中的实现方式。

1.符号

 class Symbol def to_proc Proc.new do |obj, *args| obj.send self, *args end end end 

或类似的东西 。 从上面的代码中你可以清楚地看到proc生成的对象调用方法(名称==符号)并将参数传递给方法。 举个简单的例子:

 [1,2,3].reduce(&:+) #=> 6 

这正是如此。 它执行如下:

  1. 调用:+.to_proc并获取proc对象=> #
  2. 它接受proc并将其作为块传递给reduce方法,因此不是调用[1,2,3].reduce { |el1, el2| el1 + el2 } [1,2,3].reduce { |el1, el2| el1 + el2 }它调用
    [1,2,3].reduce { |el1, el2| el1.send(:+, el2) } [1,2,3].reduce { |el1, el2| el1.send(:+, el2) }

2.方法

  class Method def to_proc Proc.new do |*args| self.call(*args) end end end 

你可以看到它有一个不同的Symbol#to_proc 。 为了说明这一点,请再次考虑reduce示例,但现在让我们看看它如何使用方法:

 def add(x, y); x + y end my_proc = method(:add) [1,2,3].reduce(&my_proc) #=> 6 

在上面的例子中调用[1,2,3].reduce { |el1, el2| my_proc(el1, el2) } [1,2,3].reduce { |el1, el2| my_proc(el1, el2) }

现在为什么map方法返回一个Array而不是一个Enumerator是因为你传递了一个块 ,试试这个:

 [1,2,3].map.class #=> Enumerator 

最后但并非最不重要的是,数组上的grep选择了===到它的参数的元素。 希望这能澄清您的疑虑。

你的顺序相当于:

 c_names = Foo.constants #=> ["Bar"] cs = c_names.map { |c_name| Foo.__send__(:const_get, c_name) } #=> [Foo::Bar] cs.select{ |c| Class === c } #=> [Foo::Bar] 

您可以将Object#method视为(粗略地):

 class Object def method(m) lambda{ |*args| self.__send__(m, *args) } end end 

grep在这里描述http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-grep

=== for Class (这是Module子类)在这里描述http://ruby-doc.org/core-1.9.3/Module.html#method-i-3D-3D-3D

更新 :你需要grep因为可以有其他常量:

 module Foo PI = 3.14 ... end 

你可能不需要它们。

Interesting Posts