`

Ruby 里的元编程

阅读更多

关于元编程

Wikipedia 上关于元编程的定义说元编程就是将程序作为数据进行处理。“用程序来处理程序”,这就是“元”的来源了,这本身是一个容易产生混淆的地方,就像“用语言来描述语言”一样,数学上的许多悖论就来自于此呢。幸好我们用的编程语言比自然语言要简单许多,并且都有严格的定义规范,有兴趣的人可以尝试在自己喜欢的编程语言里面构造一下 “This statement is false” 这个经典悖论。

回到元编程,程序处理程序可以分为“处理其他程序”和“处理自己”,对于前者,有我们熟悉的 lex 和 lacc 作为例子。而对于后者,如果再细分,可以分为“宏扩展”、“源代码生成”以及“运行时动态修改”等几种。

宏扩展从最简单的 C 语言的宏到复杂的 Lisp 的宏系统,甚至 C++ 的“模板元编程”也可以包含在这一类里面,我在这里对它们进行了一些介绍。

源代码生成则主要是利用编程语言的 eval 功能,对生成出来的源代码(除了在 Lisp 这样的语言里面以外,通常是以字符串的方式)进行求值。有一类有趣的程序 quine ,它们运行的结果就是把自己的源代码原封不动地打印出来,通常要证明你精通某一门语言,为它写一个 quine 是绝佳的选择了,这里有我搜集的一些相关资料。

最后是运行时修改,像 Lisp 、Python 以及 Ruby 之类的语言允许在运行时直接修改程序自身的结构,而不用再经过“生成源代码”这一层。当然对源代码进行 eval 的方法也许是最灵活的,但是它的效率却不怎么样,所以相来说并不是特别流行。这里主要介绍的是这种方式的元编程在 Ruby 里面的应用,如果对元编程的其他方面感兴趣,前面的几个链接应该都是很好的去处。

类与对象

Ruby 是一门完全面向对象的语言,在 Ruby 里面,所有的东西都是对象。对象是什么?对象是类的一个实例。那么类又是什么?按照前面的说法,一切都是对象,那么类也应该是一个对象喽!没错,类也是一个对象。按照前面的说法,对象应该是某一个类的一个实例,这也没错,一个特定的类就是“类(Class)”这个类的一个实例。已经开始拗口了吧?因为已经开始接触到“元”了,也就是所谓的用自己来描述自己,而描述一个类的类,通常叫做“元类(metaclass)”,不过在 Ruby 里面,元类和普通类其实是同一个东西。事实上, Ruby 还是一个单根的面向对象语言,所有的类都继承自 Object 类,包括 Class 类。运行一下下面的单元测试代码就可以验证这一点(完整的代码可以在末尾找到下载链接)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'test/unit'
 
class MetaTest < Test::Unit::TestCase
  def test_class
    str = String.new
 
    # str is an instance of the class String
    assert_equal String, str.class
    # the class String is an instance of the class Class
    assert_equal Class, str.class.class
    # the class Class is an instance of the class Class itself,
    # so metaclass is just normal class
    assert_equal Class, str.class.class.class
    # the class Class is also derived from the class Object
    assert_equal Object, str.class.class.superclass.superclass
  end
 
end

和其他面向对象语言一样。Ruby 里面对象保存成员变量的值,但是并不保存成员函数(方法),方法是放在对象所属的类里面。事实上,由于对象里面不会保存函数,Ruby 的底层实现里面类这种对象和普通对象还是有一定的区别的:

321
322
323
324
325
326
327
328
329
330
331
struct RObject {
    struct RBasic basic;
    struct st_table *iv_tbl;
};
 
struct RClass {
    struct RBasic basic;
    struct st_table *iv_tbl;
    struct st_table *m_tbl;
    VALUE super;
};

只是我们在 Ruby 里面看起来它们是一样的,或者说,类的类型派生自对象的类型,加了一个放函数的表和一个指向父类的域。

另外,还有一类方法叫做“类方法”,它不能引用到对象的成员变量,例如:

class Foo
  def Foo.func1
 
  end
 
  def Foo.func2
 
  end
end

而在类的环境里面 self 是指向类自己的引用,因此也可以用 self 代替代码中的 Foo ,像这样:

def self.func1

另外,Ruby 还允许另外一种写法:

class Foo
  class << self
    def func1
 
    end
 
    def func2
 
    end
  end
end

事实上,在类方法里面不能引用到对象的成员变量的原因很简单,因为类方法的 self 引用到特定的类,而成员方法引用到特定的对象。看下面的测试代码:

  def test_class_method
    # foo is an instance of class Foo
    foo = Foo.new
    # klass is the class of the object foo, i.e. Foo
    klass = foo.class
    assert_equal Foo, klass
 
    # class method of object foo is just instance method
    # of object klass(which is the class of foo)
    klass.set(10)
    assert_equal 10, klass.get
 
    # because get is not instance method of object foo
    assert_raises(NoMethodError) { foo.get }
  end

但是在 Foo 这个对象的类里面并不能找到 get 这个方法,如果说 getFoo 的成员方法的话,那么它应该包含在 Foo.class.instance_methods 里面才对。不过这里其实另有乾坤,关系到一个 singleton class ,我们将在后面再回到这个问题上来。

Ruby 里面的元编程

一个比较常见的例子当属 define_method 。Ruby 里面有 attr_accessor 方法,例如:

class Foo
  attr_accessor :foo, :bar
end

Foo 这个类就有了 foofoo=barbar= 这些方法,用于读取和设置 foobar 两个属性。使用起来很方便,其实一点也不神奇,用 define_method 我们立马就可以写一个,由于 Ruby 的类可以随时重新打开添加东西,我们直接修改 Class 类:

class Class
  def prop_accessor(*props)
    props.each do |prop|
      define_method prop do
        instance_variable_get "@#{prop}"
      end
      define_method "#{prop}=" do |val|
        instance_variable_set "@#{prop}", val
      end
    end
  end
end

在定义 Foo 的时候 self 是引用到 Foo (这是 Class 这个类的一个实例),因此可以使用 Class 类的成员方法,所以可以像前面的例子里面使用 attr_accessor 那样来使用这里定义的 prop_accessor ,效果是一样的。

对象真的不能持有自己的方法吗?

是的,对象不能持有自己的方法,在前面看到 Ruby 的实现代码里面 RObject 这个结构里面根本没有用于存放方法的表。但是我真的想让对象有它自己的方法,要怎么办呢?只有 RClass 结构能保存方法列表,于是就让 RObject 保存一个自己的 RClass 类就好了。这个类通常叫做“singleton class”,甚至也有叫“metaclass”的,但是通常我们认为“metaclass”是用于定义类的类,容易造成混淆,Matz 正在考虑在以后的版本里面把这个类叫做“eigenclass”,那么我们这里就称它为“eigenclass”好了。一个对象(例如 foo)的 eigenclass 与它所属的类(可以通过 foo.class 获得)有几点不同:

  • eigenclass 是对象所独有的,属于同一个类的不同对象拥有不同的 eigenclass 。
  • Ruby 目前没有提供像 foo.class 这样的方法来直接获得这个 eigenclass ,不过幸运的是我们可以很轻松地实现这个方法。
  • Ruby 会优先从 eigenclass 里面查找方法,其次才是对象所属的类以及各个祖先类。

虽然没有内置的轻松获取 eigenclass 的方法,但是将方法存放到 eigenclass 却是非常简单的,事实上语法我们已经很熟悉了。

  def test_singleton_method
    foo = Foo.new
 
    # now we put the singleton_func in the eigenclass of foo
    def foo.singleton_func
      self
    end
 
    # singleton_func exists in foo, and self refer to foo
    assert_equal foo, foo.singleton_func
    # singleton_func is not available in other instance of class Foo
    assert_raise(NoMethodError) { Foo.new.singleton_func }
  end

另外,还可以这样写

class << foo
  def singleton_func
    self
  end
end

是不是觉得似曾相识?没错,就是前面定义类方法的时候用的语法。可是那里点好前面是一个类,这里是一个对象,不是吗?没错!可是别忘了在 Ruby 里面,类也是一个对象!好了,让我们用 eigenclass 和 singleton_method 来解释一下前面的类方法的代码。

def Foo.func1

这是在 Foo 这个对象的 eigenclass 里面放入了 func1 这个方法。这也是我们在 Foo.class 里面找不到 func1 的原因了,因为它根本不在那里。现在我们已经迫不及待地想要取出 eigenclass 来看看了,怎么能容忍它偷偷地躲在那里呢?下面我们来给 Ruby 里面所有类的元祖 Object 类开开刀:

class Object
  def eigenclass
    class << self
      self
    end
  end
end

这么简单几行代码就 OK 了,在 eigenclass 方法内,打开 self(这里引用到当前的对象) 的 eigenclass ,打开以后里面那个 self 就引用到我们要的那个 eigenclass 了,注意最后一行是作为返回值的,这样我们就成功取出了 eigenclass ,赶紧测试一下!

  def test_eigenclass
    foo = Foo.new
 
    def foo.singleton_func
      self
    end
 
    # eigenclass is also an instance of the class Class
    assert_equal Class, foo.eigenclass.class
    # singleton_func is put as an instance method in the eigenclass
    assert foo.eigenclass.instance_methods.include?('singleton_func')
    # so here is the magic of the so called class method
    assert Foo.eigenclass.instance_methods.include?('get')
  end

回到类方法的问题上。我们进行如下定义以方便描述

foo = Foo.new
klass = foo.class
metaclass = klass.class
eigenclass = klass.eigenclass
  • foo 的成员方法很简单,它是存放在 klass 中的方法。也就是 klass 作为一个类所拥有的方法,调用 klassinstance_methods 就可以得到这些方法。
  • foo 的类方法,它是 klass 的成员方法,但是并不是像普通的成员方法那样存放在 metaclass 中,而是存放于 klass 的 singleton class eigenclass 中。

define_singleton_method

singleton method 并不是 Ruby 独有的,在 CLOSDylan 等语言里面也有,而像 SelfNewtonScript 之类的基于原型的语言甚至只有 singleton method 。Ruby 里面采用这种方式漂亮地实现了“类方法”。遗憾的是,目前的 Ruby 里面并没有提供一个和 define_method 同样功能的用于定义类方法的方法,我们只好自己写一个了。

class Object
  def define_singleton_method name, &body
    eigenclass.send :define_method, name, &body
  end
end

由于 define_method 是私有方法,因此我们需要使用 send 来调用。或者也可以使用 instance_eval 的方式来实现。另外,在类里面使用的时候,我们可以用一个更明确的名字:

class Class
  alias define_class_method define_singleton_method
end

Dwemthy’s Array 是一个很有趣的 Ruby 元编程的例子。仅用了几十行代码就写好了一个 RPG 游戏。在这里我们来把它的 Creature 类的 traits 类方法简化一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Creature
  def self.traits(*arr)
    return @traits if arr.empty?
 
    attr_accessor *arr
 
    arr.each do |trait|
      define_class_method trait do |val|
        @traits ||= { }
        @traits[trait] = val
      end
      define_method :initialize do
        self.class.traits.each do |k, v|
          instance_variable_set("@#{k}", v)
        end
      end
    end
  end
end

参考文献

附件

Testcase

分享到:
评论

相关推荐

    Ruby元编程第二版中文

    Ruby元编程是编程领域中一个深入且强大的主题,它允许程序员在运行时修改或创建代码,极大地提高了灵活性和代码的动态性。这本书“Ruby元编程第二版”专注于讲解Ruby语言的这一独特特性,旨在帮助开发者更好地理解和...

    ruby元编程.pdf

    ruby元编程.pdf ruby 元编程 这本书对ruby的调用原理做了非常精辟的分析。 ruby 元编程 这本书对ruby的元编程方式做了比较好的阐释。 ruby 元编程 这本书对结对编程有一个很好的提现。

    《Ruby元编程》PDF版本下载.txt

    根据提供的文件信息,本文将对《Ruby元编程》这一主题进行深入探讨,解析其核心概念、应用场景以及为何元编程在Ruby语言中具有重要的地位。 ### 一、Ruby元编程简介 #### 1.1 元编程定义 元编程是指编写能够生成或...

    Ruby元编程pdf

    Ruby元编程是编程的一种高级技巧,它允许程序员在运行时动态地修改或创建代码,极大地提高了灵活性和代码的可扩展性。Ruby作为一种动态类型语言,其元编程能力尤为强大,使得开发者可以创建出高度定制化的解决方案。...

    Ruby元编程-中文

    通过分析案例、讲解例题、回顾Ruby代码库的实现细节,作者不仅向读者展示了Ruby编程的优势和Ruby特有的解决问题的方式,更详细开列出发挥其优势的技巧和常用的Ruby设计模式。Ruby之父松本行弘作 序推荐。

    元编程 Ruby

    在Ruby这种动态编程语言中,元编程是一种核心技术,它不仅仅是一种技巧,更是Ruby和Rails框架中用于提高开发效率和软件复用性的关键方法。 在Ruby语言中,元编程主要通过方法拦截、动态方法定义、元类以及一些特殊...

    Ruby元编程中文版

    Ruby元编程中文版

    Ruby 元编程

    本书的第一部分“Ruby元编程”是本书的核心部分,他讲述了一个资深程序员在办公室一周发生的事情。 本书的第二部分“Rails中的元编程”是一个元编程实例,Rail是Ruby标志性框架。 在阅读本书之前,需要了解本书的三...

    Ruby元编程 源代码 Metaprogramming Ruby source code

    Ruby元编程是编程领域中的一个高级技术,它允许程序员在运行时操纵语言的结构和行为。这本《Metaprogramming Ruby》书籍深入探讨了如何利用Ruby的特性进行元编程,帮助开发者提升代码的灵活性、可扩展性和复用性。源...

    Ruby元编程 第2版 中文 佩罗塔著 2015.08

    ruby元编程 第2版 中文 目录清晰 方便广大通勤族路上看,如果喜欢请购买正版纸质图书

    Ruby元编程【英文版】

    ### Ruby元编程核心知识点概述 #### 一、元编程概念及意义 元编程是一种编程技术,它允许程序在运行时修改或生成其他程序的行为。在《Ruby元编程》这本书中,作者通过一系列案例来深入浅出地介绍了Ruby语言中独特...

    Metaprogramming Ruby 2nd Edition ruby元编程

    - **Kosmas Chatzimichalis**(软件工程师):本书不仅深入讲解了Ruby及其框架Ruby on Rails的核心机制,还提供了一系列宝贵的工具和技巧,帮助读者将Ruby编程水平提升到一个新的高度。 - **Arialdo Martini**...

    ruby元编程

    《Ruby元编程》以案例形式循序渐进地介绍了Ruby特有的实用编程技巧 元编程 通过分析案例 讲解例题 回顾Ruby代码库的实现细节 《Ruby元编程》作者Paolo Perrotta不仅向读者展示了Ruby编程的优势和Ruby特有的解决问题...

    基于Ruby编程语言基于Ruby编程语言基于Ruby编程语言基于Ruby编程语言.zip

    本资料包主要关注的是基于Ruby编程的基础知识和应用,包括如何使用Ruby进行开发和解决问题。 Ruby的设计哲学强调程序员的生产力和代码的可读性。它的语法直观,使得初学者能够快速上手。Ruby的核心特性包括: 1. ...

    Ruby编程语言入门与实践 .pptx

    Ruby编程语言入门与实践 Ruby编程语言入门与实践 Ruby编程语言入门与实践 Ruby编程语言入门与实践 Ruby编程语言入门与实践 Ruby编程语言入门与实践 Ruby编程语言入门与实践 Ruby编程语言入门与实践 Ruby编程语言入门...

    Ruby编程,Ruby编程,

    ### Ruby编程语言简介 #### 一、Ruby编程语言概述 Ruby是一种动态的、面向对象的、通用型的编程语言,自1995年由日本程序员松本行弘(Yukihiro Matsumoto)创建以来,便以其简洁优雅的语法、高度可读性和易于学习...

    编程之魂:Ruby中的元编程艺术

    # 编程之魂:Ruby中的元编程艺术 Ruby 是一种设计精良的高级编程语言,自1995年由日本开发者松本行弘(Yukihiro "Matz" Matsumoto)创建以来,就因其优雅简洁的语法、强大的功能以及高度的灵活性而受到众多程序员的...

Global site tag (gtag.js) - Google Analytics