13

呈现成语

我找到了一个有趣但无法解释的替代已接受答案的方法。该代码在 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#method执行方法查找并返回一个闭包。

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

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

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

    我也不确定我是否完全理解在这种特定情况下这意味着什么。为什么是 Proc?什么“技巧”,为什么它们在这里是必要的?

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

    • 为什么这个 #map 构造返回一个 greppable 数组而不是一个枚举器?

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

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

问题,重述

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

4

4 回答 4

9

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

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,它不会被grepped。

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

于 2012-07-16T14:09:20.257 回答
4

您对成语的解析非常准确,但我会仔细阅读并尝试澄清您提到的任何问题。

1.Foo.constants

正如您所提到的,这将返回一个模块常量名称数组作为符号。

2.Array#map

您显然知道这是做什么的,但为了完整起见,我想将其包括在内。Map 接受一个块并以每个元素作为参数调用该块。它返回Array这些块调用的结果。

3.Object#method

同样正如您所提到的,这会进行方法查找。这很重要,因为在 Ruby 中不带括号的方法是对该方法的不带任何参数的方法调用。

4.&

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

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

5.grep(Class)

工作方式Enumerable#grep是它对argument === x可枚举中的所有 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!)时返回,这将过滤掉不属于Moduleor类型的常量ClassModule#=== 您可以在此处找到文档。

于 2012-07-31T00:48:51.033 回答
3

首先要知道的是:

&调用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 对象=> #<Proc:0x007fea74028238>
  2. 它接受 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 是因为你传递给它一个 block,试试这个:

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

最后但并非最不重要的grepArray 是选择===与其参数有关的元素。希望这能澄清您的担忧。

于 2012-07-30T10:50:05.593 回答
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

===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

你可能不需要它们。

于 2012-07-16T14:13:47.073 回答