在内省模块类时,"#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. CCD_ 2返回一个模块常量数组作为符号。

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

  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]
      

问题,重述

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

&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.constants0

这个运算符用于将事物转换为块。我们需要这样做,因为块在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#===ClassMethod继承了其===方法)返回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调用对象上的方法(name==符号),并将参数传递给该方法。举个简单的例子:

[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是因为您正在向它传递块,请尝试以下操作:

[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

此处描述grephttp://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-grep

这里描述了ClassModule的子类)的===http://ruby-doc.org/core-1.9.3/Module.html#method-i-3D-3D-3D

UPDATE:您需要grep,因为可能还有其他常量:

module Foo
  PI = 3.14
  ...
end

你可能不需要它们。

相关内容

  • 没有找到相关文章

最新更新