呈现成语
我找到了一个有趣但无法解释的替代答案。代码显然可以在REPL中工作。例如:
module Foo
class Bar
def baz
end
end
end
Foo.constants.map(&Foo.method(:const_get)).grep(Class)
=> [Foo::Bar]
然而,我不完全理解这里使用的成语。特别是,我不理解&Foo
的使用,它似乎是某种闭包,也不理解#grep的特定调用是如何对结果进行操作的。
解析成语
到目前为止,我已经能够解析其中的一些细节,但我并没有真正看到它们是如何结合在一起的。以下是我对示例代码的理解。
CCD_ 2返回一个模块常量数组作为符号。
method(:const_get)
使用Object#方法执行方法查找并返回闭包。Foo.method(:const_get).call :Bar
是一个闭包,它返回类中常量的限定路径。&Foo
似乎是某种特殊的lambda。医生说:&如果Proc对象由&论点
我也不确定我是否完全理解在这种特定的背景下这意味着什么。为什么是Proc?什么"把戏",为什么它们在这里是必要的?
grep(Class)
是对#map方法的值进行操作的,但其特征并不明显。为什么这个#map构造返回一个可greppable数组而不是枚举器?
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(:+)
#=> #<Method: Fixnum#+>
m.call(1)
#=> 2
(1..3).map(&m)
#=> [2, 3, 4]
所以最后,这只是一种无意义的Foo.constants.map { |c| Foo.const_get(c) }
表达方式。grep
使用===
来选择元素,所以它只会得到引用类的常量,而不会得到其他值。这可以通过向Foo
添加另一个常数来验证,例如Baz = 1
,它不会得到grep
ped。
如果你还有其他问题,请将其作为评论添加,我会尽力澄清。
您对习语的分析非常准确,但我会仔细阅读并尝试澄清您提到的任何问题。
1.Foo.constants
正如您所提到的,这将以符号形式返回一个模块常量名称数组。
2.Array#map
你显然知道它的作用,但为了完整性,我想把它包括在内。Map获取一个块,并将每个元素作为参数调用该块。它返回这些块调用的结果的Array
。
3.Object#method
正如您所提到的,这将执行方法查找。这一点很重要,因为Ruby中没有括号的方法是该方法的方法调用,没有任何参数。
4.Foo.constants
0
这个运算符用于将事物转换为块。我们需要这样做,因为块在Ruby中不是一流的对象。由于这种二等地位,我们无法创建独立的块,但我们可以将Procs
转换为块(但只有当我们将其传递给函数时)!&
运算符是我们进行此转换的方法。每当我们想把Proc
对象当作一个块来传递时,我们都可以用&
运算符对其进行预处理,并将其作为函数的最后一个参数来传递。但是&
实际上可以转换的不仅仅是Proc
对象,它可以转换任何有to_proc
方法的对象!
在我们的例子中,我们有一个Method
对象,它确实有一个to_proc
方法。Proc
对象和Method
对象之间的区别在于它们的上下文。Method
对象绑定到一个类实例,并可以访问属于该类的变量。CCD_ 33被绑定到创建它的上下文;也就是说,它可以访问创建它的范围。CCD_ 34绑定该方法的上下文,使得得到的CCD_。您可以在此处找到有关&
运算符的更多信息。
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
!)时,Module#===
(Class
从Method
继承了其===
方法)返回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调用对象上的方法(name==符号),并将参数传递给该方法。举个简单的例子:
[1,2,3].reduce(&:+)
#=> 6
正是这样。它的执行方式如下:
- 调用
:+.to_proc
并返回一个proc对象=> #<Proc:0x007fea74028238>
- 它获取proc并将其作为块传递给
reduce
方法,因此它不调用[1,2,3].reduce { |el1, el2| el1 + 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) }
。
现在,为什么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
这里描述了Class
(Module
的子类)的===
http://ruby-doc.org/core-1.9.3/Module.html#method-i-3D-3D-3D
UPDATE:您需要grep
,因为可能还有其他常量:
module Foo
PI = 3.14
...
end
你可能不需要它们。