当内省模块类时,“#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的这种特定调用如何对结果进行操作。
解析成语
到目前为止,我已经能够解析其中的一些部分,但我并没有真正看到它们如何组合在一起。 以下是我认为我对示例代码的理解。
-
Foo.constants
返回模块常量数组作为符号。 -
method(:const_get)
使用Object#方法执行方法查找并返回闭包。 -
Foo.method(:const_get).call :Bar
是一个闭包,它返回类中常量的限定路径。 -
&Foo
似乎是某种特殊的lambda 。 文档说:如果proc对象由&参数给出,&参数将保留技巧。
我不确定我是否完全明白在这个特定背景下这意味着什么。 为什么选择Proc? 什么“诡计”,为什么这里有必要?
-
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#===
( Class
从Method
inheritance其===
方法)当参数是Module
一个实例或其后代之一(如Class
!)时,它返回True
,它将过滤掉不属于Module
或Class
类型的常量。 您可以在此处找到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
这正是如此。 它执行如下:
- 调用
:+.to_proc
并获取proc对象=> #
- 它接受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
你可能不需要它们。