初窥Ruby Metaprogramming
接触了一段时间得ruby on rails,深深被ror的magic,powerful,elegantly所折服,同时也对ruby这个神奇的语言本身产生了很大的好奇心,而其中最神奇的莫过于ruby 的 Metaprogramming。
- Classes are open
我们先看一段代码:
1 2 3 4 5 6 7 | class String def say_hello p "Hello!" end end "Fred" .say_hello |
这里我们看到我们reopen了String这个build-in的class,而且添加了一个新的方法say_hello(.NET 3.5中通过扩展方法也实现了这个特性,但ruby的实现更加自然和灵活)这样使得ruby语言自身提供了很大的可扩展性,而这种从编程语言层面提供的可扩展性为好处体现在两个方面。
第一,对于ruby语言自身,在其以后的版本中可以对原有类在不破坏原有代码的基础之上提供更多更好的方法。.NET 3.5 已经通过扩展方法这个新特性,在原有集合类的方法之外增加了一些新的查询方法。
第二,对于ruby的使用者,也就是我们这些ruby程序员来说。classes are open,这就意味我们可以更加实现我们一些具体的特殊的需求。例如,我们希望我们应用的程序中的String都可以提供一个encrype的方法,来实现加密。又或者我们对于String类的to_s方法的实现觉得不够满意,我们都可以reopen String这个类,然后定义我们的方法。因为ruby的方法查找遵循
”Define a method twice inside the same class, the second method definition takes precedence“
所有我们毋需担心,我们对于to_s的调用出问题。
前面我说道,ruby的open class比.NET提供的扩展方法更加灵活。而这个灵活体现在我们可以针对一个instance去增加方法,如下
1 2 3 4 5 6 | fred = 'fred' def fred.say_hello p 'hello' end fred.say_hello |
- Definition are active
1 2 3 4 5 6 7 8 9 10 11 | class Logger if ENV [ 'debug' ] def log 'debug' end else def log 'non-debug' end end end |
这是一段非常简单的代码,但是我们可以看到我们是否定义debug这个ENV对于我们的程序会有完全不一样的行为。这里也许有人会说静态语言的条件编译同样能完成这样的任务。那么我们就再看一段代码
1 2 3 4 5 6 | result = class Fred puts 'Hello' x = 3 end puts result |
执行这段代码,我们会看到这样的输出结果:
Hello
3
为什么会输出Hello呢?因为definition are active,也就是定义本身就是一段可执行的代码。为什么会输出3呢?因为ruby中所有的可执行代码都会有返回值。到这里肯定会有人问,那么class定义中的method呢?你可以试试在irb中定义一个method,你会发现在irb会返回一个nil给你。
但是definition are active在我们实际开发中有什么用呢?那让我们看一下一个rails的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | module ActiveRecord class Base def has_many models end def belongs_to model end end end class Order < ActiveRecord::Base has_many :items end class Item < ActiveRecord::Base belongs_to :order end |
- All methods have a receiver
在ruby中,方法的调用是以message的形式发送给相应的instance的。比如说foo.hello(),就是发送hello这个message给foo。这里很多人会好奇,那么如果我在irb上直接定义方法呢?其实ruby里面有一个概念叫top level execution, 它是一个Object的instance叫做main。当你直接在irb中定义一个方法或者执行一个方法(例如puts "hello"),同样你只是发送了一个message,而这个message的receiver就是top level execution。
ruby代码的执行是与当前代码所在context相关,不同的context关联不同的receiver。也就是当你的代码在不同的context下执行,由于context关联的receiver不同也就有了不同的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Context def name "smith" end p name def hi p name end end Context. new .hi |
结果为:
"Context"
"smith"
如果你想知道在你当前context下你方法的receiver,可以通过在当前context下调用self来获得。
- Class are Object
我们都知道一个object有什么样的行为和属性是在ruby中由它的class决定。比如
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Person attr_reader :name def initialize(name) @name = name end def introduce "I'm #{@name}." end end p = Person. new "Dave" |
对于这个例子中,p具有什么样的行为和属性是由Person这个class决定的。可是我们看到对于Person我们调用了一个new的方法,那么这个new方法是由谁定义的呢?很简单啊,我们知道p的行为和属性由它的class也就是Person决定,那么Person的new方法应该也来自它的class。也就是引出了Class对象,Class对象中有两个new方法,一个是class method另一个是instance method。我们的Person.new自然调用的就是Class对象中叫new的instance method, 那么那个叫做new的class method有什么用呢?
1 2 3 4 5 6 7 8 9 10 11 | Person = Class . new do attr_reader :name def initialize(name) @name = name end def introduce "I'm #{@name}." end end |
这段代码可以实现之前那段代码一摸一样的功能,而这里调用的就是Class中叫做new的class method。最奇怪的Class的superclass是Module,而Module的superclass是Object,但是Class的class是自身,Module的class是Class,而Object的class也是Class(superclass是Class的方法,class是Object的方法),我们也可以说ruby中所有的Object的class都是Class(nil的class是NilClass,但是NilClass的class是Class)。Class间接继承Object,但是Object的class又是Class,一个典型“鸡生蛋,蛋生鸡”的问题。这个问题给我最大困惑则是:如果我调用一个对象例如上面例子中p的XX方法,而这个XX方法并没有直接在Person中定义,那么这个XX方法是来自Class还是Object呢?而对于这一点ruby的解决办法是在方法的查找receiver的时候,会先检查Person有没有这个XX方法,会先检查Class后检查Object,也就是先检查一个class的class,然后检查superclass。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?