Ruby元编程 星期一
@ Shen Jianan · Saturday, Oct 24, 2015 · 8 minute read · Update at Oct 24, 2015

转入魔都

啊哈~自从不做阿里实习生之后,在学校浑浑噩噩过了一周左右,终于又开始找实习~先投了猿题库,无奈要等到十月中旬才能面试,就先面了eBay,没想到还挺顺利地就过了,就这么当着杭州的二房东跑到上海开始实习生涯了~~ P.S. 6号拿到offer13号入职,到现在才写下这段话的效率也是堪忧啊~

初遇Ruby

说是Ruby元编程,是打算写下读《Ruby元编程》的一些笔记,但是在这之前,还是要先讲讲怎么会开始学习Ruby了呢~

10月13日,那是一个风和日丽的早晨。来到eBay之前就风闻是个很闲适的公司,闲适到七爷得知我到eBay实习后的第一回应就是“哦!很清闲!”。当主管和我聊到需要做的事情的时候,才知道需要使用Ruby开发,说实话心里还有点小激动“早听说Ruby很厉害,我要成为那个不拘一格的一员了么~”。其实一般都会有这样的心态:使用越小众的语言就显得越厉害…当然残存的理智告诉我,程序员是不可能因为换个语言而变厉害的,更何况一个连Java都谈不上精通的程序员。

学之前还是挺忐忑的,但是我那慵懒的,十二点左右上班,六点左右下班的mentor跟我说“这玩意儿很简单的,你随便看看就会了”。于是我收拾心情,花了一个晚上看了一下Ruby语法,诶~感觉跟python还蛮像的,可能还真的很简单呢!

就这样,我要开始我的Ruby学习之旅了~!
上面这样的句式总是会让人感觉你现在已经是某某的大师,其实我现在还什么都不懂哈哈!

Ruby元编程

好了,终于开始正篇。

其实一个语言的语法很简单,有一本书大致叫《七天学习七门语言》,讲的就是七天内掌握七门语言的语法,这对于熟悉一门编程语言的人来说其实并不难。但语言的精髓其实在于它的设计思想,当真正领会了一门语言的设计思想之后,我想以后学习其他语言的时候,不仅能很快掌握语法,更能迅速地利用这门语言的特性写出优秀的代码吧。所以在看了“Ruby菜鸟教程”这种语法科普之后,我就开始看《Ruby元编程》啦,因为是松本行弘本人写的,看网上的评价也非常好,所以…希望能从中一窥Ruby号称的“元编程”到底比起Java等语言神奇在何处。

《Ruby元编程》其实是一个故事…书中主人公跟我一样是一个刚进公司的实习生,和他结对编程兼任mentor的是一个Ruby大师~然后他们的故事以一天为一章…其实书很薄,当然也要看有没有足够的时间,所以~就按照章节来写笔记吧。

今天是“星期一”。

内省

在很多语言,比如C++中,语言构件(变量、类、方法等)在编译器完成工作之后,就变成了看不见摸不着的东西,只是内存位置而已。

而在Ruby和其他很多语言中,运行时绝大多数的语言构件依然存在。可以从构件处获得关于它自己的信息。这称为内省。

内省的例子:

class Greeting 
	def initialize(text) 
		@text = text 
	end  
	
	def welcome 
		@text 
	end
end 

my_obj = Greeting.new("Hello")


puts my_obj.class ,my_obj.class.instance_methods(false) ,my_obj.instance_variables

通过这样的代码,就可以获得关于对象本身的很多信息。这样说来,Java也是内省的语言。

C在运行时中,只有一大堆机器码。在C++中,一些语言构件可以在编译后留下来。Java中,编译时和运行时界限十分模糊,可以有足够的内省能力来列出一个类的方法和继承关系等。

最彻底的是Ruby,Ruby中没有编译时的概念,所有的构件都在运行时可用。

元编程

比起内省,元编程更进一步,它允许对语言构件进行修改,举个例子:

在Java中, 假如想要写一个DBModel,必须要把数据库中的字段都一一对应写在类中,并添加getter和setter。而在数据库字段发生改变的时候,还必须相应地改变类的实例变量和对应的实例方法。而在Ruby中,只要写出如下的代码,就可以自动完成数据表的映射:

class Movie < ActiveRecord::Base
end

这样就能直接使用Movie#title()来访问title字段。这些方法和变量都没有定义过,那么ActiveRecorder是怎么实现的呢?

因为类名是Movie,ActiveRecord会把它映射到名为movies的表中。而那些访问属性的方法,是ActiveRecord从表模式中得到字段名后,自动定义的方法。

写入语言构件的能力就在这时候发挥作用了,至于这种能力是怎么实现的,在后面的书中会进行讲解。

元编程是编写在运行时操纵语言构件的代码。

这就是元编程的概念~

Open Class特性

Open Class特性 在Ruby中,第二次定义一个类只会修改已经存在的类,比如:

class D
    def x
    end
end

class D
    def y
    end
end

这样的话,D就有x和y两个方法了。

值得注意的是…这样很可能导致原来其他地方使用的方法被重写,从而导致运行出现问题。这是Open Class的隐患,经常被称作猴子补丁 Monkeypatch

为了避免猴子补丁的问题,我们可以将类放在模块中,这样就能很大程度上避免命名冲突了。(还是有可能会发生问题:只要两处的模块名相同且类名相同,就依然会有猴子补丁的问题)

另外还有其他的解决方案,选择子空间等等…当然,书中的mentor说在书后面会讲到,那就后面看到再说吧~

Ruby类的真相

在Ruby这样的动态语言中,对象的类和实例变量没有关系,当给实例变量赋值时,它们就生成了。如下例,输出是如果不曾调用my_method,那么输出的变量列表就是空

class MyClass 
	def my_method 
		@v=1 
	end
end 

obj = MyClass.new  
puts obj.instance_variables

实例变量存放在对象中,而方法(包括类方法和对象的方法)放在类中。

类自身也是对象。类自身的类叫做Class. 在这里类是一个对象,那么这个对象的类:Class也就拥有了这个对象的实例方法。所以一个类的方法就是Class的实例方法。

类继承关系

Class的superclass是Module Module的superclass时Object Object的superclass是BasicObject……MyClass BasicObject的superclass是nil

因此,Class只不过是在Module的基础上增加了new、allocate和superclass这三个方法的增强模块罢了。

可是运行Module.methods可以找到这三个方法?

首先,methods,private_methods是Object类的实例方法;instance_methods是Module类的实例方法。 Ruby对象可以分为2类,一类是普通对象,还有一类对象是类(类本身也是一种对象),像String,Class这种类,这种对象叫类对象。 普通对象的祖先链,以"abc"为例,为String-> Comparable->Object->Kernel-> BasicObject 类对象的祖先链,以String为例,为Class->Module->Object->Kernel-> BasicObject 我们可以看到普通对象是没有instance_methods方法的,因为其祖先链上没有Module类。所以对于一个普通对象,我们只能说它有方法或私用方法,而不能说它有实例方法,实例方法是对一个类来说的。 (引用自slef-motivation的博客https://blog.csdn.net/happyanger6/article/details/42436879)

所以运行Module.methods找到的是Object类中的方法,那时候自然会有这三个方法。在Module.instance_methods中就找不到这三个方法了。

Module类提供了两个constants()方法,实例方法Module#constants()返回当前范围内的常量,而类方法Module.constants()返回当前程序中所有的顶级常量,包括类名。

module MyClass   AAA = 'aaa'   def my_method  @v=1  end end  
MyClass.constsants #返回AAA
Module.constants 	#返回Object,Module,Class,BasicObject......MyClass。这些都是顶级常量(类名也是顶级常量),而AAA由于不是顶级的常量,所以没有被列在里面

当在一个类中包含一个模块时,Ruby创建了一个封装该模块的匿名类,并把这个匿名类插入到祖先链中。

module M  
	def my_method   
	end 
end  

class C  
	include M 
end  

puts C.ancestors

结果输出:

C
M
Object
Kernel
BasicObject

祖先链会包含模块,在这里是M和Kernel

那么Kernel模块又是什么呢?

Ruby中有像print()这类方法,我们可以在任何代码空间中调用它们,这实际上都是Kernel模块的私有方法。Object类包含了Kernel模块,而Object类是任何类的superclass,所以任何类都会包含Kernel模块,也就有了Kernel内的私有方法。

self

由于对象和类的方法分开,对象必须要有一个self引用才能让方法知道当前调用者是谁。

通常self是最后一个接收到方法调用的对象,但是当在类和模块中定义时,self的角色由类/模块担任。

class MyClass
    self                    # => MyClass
end

所以在类中定义方法前加上self就是类方法/单例方法了,因为这时self是类自己

class MyClass
  def self.claz_method
    p 'this is a class method'
  end

end

MyClass.claz_method

puts MyClass.singleton_methods(false)  # => claz_method

Ruby中特殊的private规则

Ruby中不允许明确指定一个接收者来调用一个私有的方法。换言之,每次调用一个私有方法时,只能调用于隐含的接收者——self上。

class C  
	def public_method  
		self.private_method 
	end   
	
	private  
	def private_method   
	end 
end   

C.new.public_method

就算这样,指定self,依然会调用失败,因为要求只能调用于隐含的self。

关于self的一个案例

module Printable  
	def print
	  puts 'print in Printable'
	end 
end  

module Document
  def print_to_screen
    print 
  end   
  
  def print  
    puts 'print in document'  
  end 
end  

class Book  
	include Document  
	include Printable 
end   

Book.new.print_to_screen

这里的输出结果是"print in Printable”

因为祖先链是:[Book, Printable, Document, Object , Kernel, BasicObject]

所以self在print_to_screen中寻找print方法时,由于self所属的Book类没有print方法,所以向从Book类向上寻找,而到Printable就已经找到方法了,就会调用Printable中的print方法。

总结

这就是今天的《Ruby元编程》抄写情况,谢谢观看~

About Me

2018.02至今 杭州嘉云数据 算法引擎

2017.6-2017.12 菜⻦网络-⼈工智能部-算法引擎

2016.09-2018.06 南京大学研究生

2015.07-2015.09 阿里巴巴-ICBU-实习

2012.09-2016.06 南京大学本科