我找到了一个interesting but unexplained alternative来接受答案。代码清楚地在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#method执行方法查找并返回闭包。
Foo.method(:const_get).call :Bar
是一个闭包,它返回类中常量的限定路径。
&Foo
似乎是some sort of special lambda。文档说:
&如果Proc对象由&给出,则参数保留技巧。参数。
我不确定我是否完全理解这在特定背景下的含义。为什么选择Proc?什么“诡计”,为什么这里有必要?
grep(Class)
正在对#map method的值进行操作,但其功能并不明显。
为什么这个#map构造返回一个greppable Array而不是Enumerator?
Foo.constants.map(&Foo.method(:const_get)).class
=> Array
如何为一个名为Class的类的grepping实际工作,为什么这里需要特定的构造?
[Foo::Bar].grep Class
=> [Foo::Bar]
我真的很想完全理解这个习语。任何人都可以在这里填补空白,并解释这些部分是如何组合在一起的吗?
答案 0 :(得分:9)
&Foo.method(:const_get)
是const_get
对象的方法Foo
。这是另一个例子:
m = 1.method(:+)
#=> #<Method: Fixnum#+>
m.call(1)
#=> 2
(1..3).map(&m)
#=> [2, 3, 4]
所以最后这只是pointfree说Foo.constants.map { |c| Foo.const_get(c) }
的方式。 grep
使用===
来选择元素,因此它只会获得引用类的常量,而不是其他值。这可以通过向Foo
添加另一个常量来验证,例如Baz = 1
,不会获得grep
ped。
如果您还有其他问题,请将其添加为评论,我会尝试澄清它们。
答案 1 :(得分:4)
你对这个成语的解析非常有用,但我会仔细阅读并尝试澄清你提到的任何问题。
Foo.constants
正如您所提到的,这将返回一个模块常量名称数组作为符号。
Array#map
你显然知道这是做什么的,但我想把它包括在内以保证完整性。 Map接受一个块并调用阻塞,每个元素作为参数。它返回Array
这些块调用的结果。
Object#method
正如您所提到的,这会进行方法查找。这很重要,因为Ruby中没有括号的方法是该方法的方法调用,没有任何参数。
&
此运算符用于将事物转换为块。我们需要这个,因为块不是Ruby中的第一类对象。由于这种二级状态,我们无法创建独立的块,但我们可以将Procs
转换为块(但只有当我们将它们传递给函数时)! &
运算符是我们进行此转换的方式。每当我们想要传递一个Proc
对象就像它是一个块一样,我们可以在它前面添加&
运算符,并将它作为函数的最后一个参数传递给它。但&
实际上可以转换的不只是Proc
个对象,它可以转换任何具有to_proc
方法的内容!
在我们的例子中,我们有一个Method
对象,它有一个to_proc
方法。 Proc
对象和Method
对象之间的区别在于它们的上下文。 Method
对象绑定到类实例,并且可以访问属于该类的变量。 Proc
绑定到创建它的上下文;也就是说,它可以访问创建它的范围。 Method#to_proc
捆绑了方法的上下文,以便生成的Proc
可以访问相同的变量。您可以找到有关&
运算符here的更多信息。
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
方法)当参数是True
的实例或其中之一时返回Module
它的后代(如Class
!),它将过滤掉不属于Module
或Class
类型的常量。
您可以找到Module#===
here的文档。
答案 2 :(得分:3)
首先要知道的是:
&
在接下来的对象上调用to_proc
并使用作为方法块生成的proc。
现在,您必须深入了解在特定类中实现to_proc
方法的具体方式。
class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end
或something like this。从上面的代码中你可以清楚地看到proc生成的对象调用方法(名称==符号)并将参数传递给方法。举个简单的例子:
[1,2,3].reduce(&:+)
#=> 6
正是如此。它执行如下:
:+.to_proc
并获取一个proc对象=> #<Proc:0x007fea74028238>
reduce
方法,因此不是调用[1,2,3].reduce { |el1, el2| el1 + el2 }
而是调用[1,2,3].reduce { |el1, el2| el1.send(:+, el2) }
。 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) }
。
现在为什么map
方法返回一个数组而不是枚举数是因为you are passing it a block,请尝试这样做:
[1,2,3].map.class
#=> Enumerator
最后但并非最不重要的是,数组上的grep
正在为其参数选择===
个元素。希望这能澄清您的担忧。
答案 3 :(得分:2)
您的序列相当于:
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
===
Class
的子Module
的{{1}} http://ruby-doc.org/core-1.9.3/Module.html#method-i-3D-3D-3D
更新:您需要grep
,因为可能还有其他常量:
module Foo
PI = 3.14
...
end
你可能不需要它们。