当内省模块类时,“#map(& proc)”成语如何工作?

时间:2012-07-16 13:54:08

标签: ruby constants introspection idioms proc

介绍成语

我找到了一个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的这种特定调用如何对结果进行操作。

解析成语

到目前为止,我已经能够解析其中的一些部分,但我并没有真正看到它们是如何组合在一起的。以下是我认为我对示例代码的理解。

  1. Foo.constants返回一个模块常量数组作为符号。

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

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

  4. &Foo似乎是some sort of special lambda。文档说:

      

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

    我不确定我是否完全理解这在特定背景下的含义。为什么选择Proc?什么“诡计”,为什么这里有必要?

  5. 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]
      
  6. 问题,重述

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

4 个答案:

答案 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]

所以最后这只是pointfreeFoo.constants.map { |c| Foo.const_get(c) }的方式。 grep使用===来选择元素,因此它只会获得引用类的常量,而不是其他值。这可以通过向Foo添加另一个常量来验证,例如Baz = 1,不会获得grep ped。

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

答案 1 :(得分: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对象绑定到类实例,并且可以访问属于该类的变量。 Proc绑定到创建它的上下文;也就是说,它可以访问创建它的范围。 Method#to_proc捆绑了方法的上下文,以便生成的Proc可以访问相同的变量。您可以找到有关&运算符here的更多信息。

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方法)当参数是True的实例或其中之一时返回Module它的后代(如Class!),它将过滤掉不属于ModuleClass类型的常量。 您可以找到Module#=== here的文档。

答案 2 :(得分:3)

首先要知道的是:

&在接下来的对象上调用to_proc并使用作为方法块生成的proc。

现在,您必须深入了解在特定类中实现to_proc方法的具体方式。

1。符号

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

正是如此。它执行如下:

  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) }
  3. 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方法返回一个数组而不是枚举数是因为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

你可能不需要它们。