WARNING: The following is the study notes of "MetaProgramming Ruby". As the only academic research, if you like the content, please support genuine.
以下内容为《Ruby元编程》的读书笔记,仅作为学术研究,如果您喜欢以下内容,请支持正版.
I. The object model
Open Class
1
2
3
4
5
class String
def to_alphanumeric
gsub /[^\w\s]/ , ''
end
end
Inside Class Definitions
1
2
3
4
5
3 . times do
class C
puts 'hello'
end
end
class关键字把你带到类的上下文,让你可以在其中定义方法。
What's in an Object
1
2
3
4
5
6
7
8
class MyClass
def my_method
@v = 1
end
end
obj = MyClass . new
obj . class # => MyClass
实例变量
1
2
obj . my_method
obj . instance_variabes #=> [:@v]
方法
1
obj . methods . grep ( /my/ ) # => [:my_method]
一个对象仅包含它的实例变量和一个对自身类的引用。
方法存放在类中,而非对象中。
1
2
String . instance_methods == "abc" . methods # => true
String . methods == "abc" . methods # => false
Classes Revisited
类自身也是对象。类也有自己的类,名为Class。类的方法就是Class的实例方法。
1
2
"abc" . class # => String
String . class # => Class
1
2
3
String . superclass # => Object
Object . superclass # => BasicObject
BasicObject . superclass # => nil
1
2
Class . superclass # => Module
Module . superclass # => Object
1
2
class MyClass ; end
obj1 = MyClass . new
obj1 和 MyClass 都是引用,唯一区别在于,obj1是一个变量,而MyClass是一个常量。
Constants
任何以大写字母开头的引用(包括类名和模块名),都是常量。
常量像文件系统一样组织成树形结构。模块(类)像目录,而常量像文件。
常量可以通过路径方式来唯一标示。使用双冒号进行分割。
1
2
3
4
5
6
7
module M
class C
X = 'a constant'
end
C : :X
end
在常量前加上一组冒号标示根路径
1
2
3
4
5
module M
Y = 'another constant'
end
:: M : :Y
Method Lookup
接受者(receiver) :你调用方法所在的对象, 例如mystring.reverse()中,my string就是接收者。
祖先链(ancestors chain) :一个类移动到它的超类,再移动到超类的超类,依此类推,直到到达BasicObject类。
1
2
3
4
5
6
7
8
9
10
11
12
13
module M
def my_method
'M#my_method()'
end
end
class C
include M
end
class D < C ; end
D . new . my_method () # => 'M#my_method()'
include一个模块时,Ruby创建了一个封装该模块的匿名类,并把这个匿名类插入到祖先链中,在它的类上方。
D.ancestors #=> [D, C, M, Object, Kernel, BasicObject]
封装(wrapper)类叫做包含类(include class) ,也叫代理类(proxy class) 。
Method Execution
每一行代码都会在一个对象中被执行——这个对象就是当前对象。当前对象可以用self表示。
调用一个方法时,接收者就成为self。
在类和模块定义中(并在任何方法定义之外),self为当前模块。
1
2
3
class MyClass
self # => MyClass
end
实例变量永远都被认定为self的实例变量。
没有明确指定接收者的方法调用,都当成是调用self的方法。
II. Methods
Calling methods Dynamically
1
2
3
4
5
6
7
8
class MyClass
def my_method ( my_arg )
my_arg * 2
end
end
obj = MyClass . new
obj . my_method ( 3 ) # => 6
1
obj . send ( :my_method , 3 ) # => 6
通过send()方法,调用的方法名可以成为一个参数。
在代码运行期间,最后一刻才决定调用哪个方法,叫动态派发(Dynamic Dispatch) 。
send()可以调用任何方法,甚至私有方法。在不使用上下文探针(Context Probe) 情况下,这是查看私有事物最简单的方法。
Ruby 1.9.1新增了public_send()
方法,尊重接收者的隐私权。
Defining Methods Dynamically
可以用Module#define_method()
方法定义一个方法。
1
2
3
4
5
6
7
8
class MyClass
define_method :my_method do | my_arg |
my_arg * 3
end
end
obj = MyClass . new
obj . my_method ( 2 ) # => 6
在运行时定义方法的技术称为动态方法(Dynamic Method)
method_missing()
1
2
3
4
5
6
class Lawyer ; end
nick = Lawyer . new
nick . talk_simple
=> NoMethodError : undefined method 'talk_simple' for #<Lawyer:0x3c848> [...]
Kernel#method_missing()
方法抛出一个NoMethodError。
Ghost Methods
被method_missing()
方法处理的消息,从调用者角度看,跟普通方法没有区别,但是实际上接收者并没有相对应的方法。这被称为一个幽灵方法(Ghost Method) 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyOpenStruct
def initialize
@attributes = {}
end
def method_missing ( name , * args )
attribute = name . to_s
if attribute =~ /=$/
@attributes [ attribute . chop ] = args [ 0 ]
else
@attributes [ attribute ]
end
end
end
icecream = MyOpenStruct . new
icecream . flavor = 'vanilla'
icecream . flavor # => "vanilla"
Dynamic Proxies
一个捕获幽灵方法调用并把它们转发给另外一个对象的对象(有时也会在转发前后包装一些自己的逻辑),称为动态代理(Dynamic Proxy) 。
使用Ruby的delegate库快速实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
require 'delegate'
class Assistant
def initialize ( name )
@name = name
end
def read_email
" #{ @name } read email."
end
def check_schedule
" #{ @name } check schedule."
end
end
class Manager < DelegateClass ( Assistant )
def initiailize ( assistant )
super ( assistant )
end
def attend_meeting
"attend meeting."
end
end
frank = Assistant . new ( "Frank" )
anne = Manager . new ( frank )
anne . attend_meeting # => "attend meeting."
anne . read_email # => "Frank read email."
anne . check_schedule # => "Frank check schedule."
DelegateClass()
是一种拟态方法(Mimic Method) ,这种方法创建并返回一个新的Class。这个类会定义一个method_missing()
方法,并把对它发生的调用转发到被封装的对象上。
When Methods Clash
当一个幽灵方法和一个真实方法发生名字冲突时,后者胜出。
为安全起见,应该在代理类中删除绝大多数继承来的方法,这就是白板(Blank Slate)类 。
有两种途径删除一个方法。使用Module#undef_method()
方法,它会删除所有的(包括继承来的)方法;使用Module#remove_method()
方法,它只会删除接收者自己的方法,而保留继承来的方法。
Ruby 1.9 开始,白板技术被集成到语言自身中。 从BasicObject继承来的类会自动称为白板类。
1
2
BasicObject . instance_methods
=> [ :== , :equal? , : ! , : != , :instance_eval , :instance_exec , :__send__ , :__id__ ]
Wrapping It Up
使用动态方法 和动态派发 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Computer
def initialize ( computer_id , data_source )
@id = computer_id
@data_source = data_source
data_source . methods . grep ( /^get_(.*)_info/ ) { Computer . define_component $ }
end
def self . define_component ( name )
define_method ( name ){
info = @data_source . send "get_ #{ name } _info" , @id
price = @data_source . send "get_ #{ name } _price" , @id
result = " #{ name . capitalize } : #{ info } ($ #{ price } )"
return " * #{ result } " if price >= 100
result
}
end
end
使用动态代理 和白板 技术。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Computer
instance_methods . each do | m |
undef_method m unless m . to_s =~ /^_|method_missing|respond_to?/
end
def initialize ( computer_id , data_source )
@id = computer_id
@data_source = data_source
end
def method_missing ( name , * args )
super if ! respond_to? ( name )
info = @data_source . send ( "get_ #{ name } _info" , args [ 0 ] )
price = @data_source . send ( "get_ #{ name } _price" , args [ 0 ] )
result = " #{ name . to_s . capitalize } : #{ info } ($ #{ price } )"
return " * #{ result } " if price >= 100
result
end
def respond_to? ( method )
@data_source . respond_to? ( "get_ #{ method } _info" ) || super
end
end
III. Blocks
Back to the Basics
1
2
3
4
5
def a_method ( a , b )
a + yield ( a , b )
end
a_method ( 1 , 2 ) { | x , y | ( x + y ) * 3 } # => 10
大多数程序员会对只有一行的块使用大括号,而对多行的块使用do..end关键字。
只有在调用一个方法时才可以定义一个块。块会被直接传递给这个方法,然后该方法可以用yield
关键字回调这个块。块中最后一行代码执行的结果会被作为返回值。
通过Kernel#block_given?
询问当前的方法调用是否包含块。
1
2
3
4
5
6
7
def a_method
return yield if block_given?
'no block'
end
a_method # => "no block"
a_method { 'here' s a block! ' } # => "here' s a block! "
Closures
当代码运行时,它需要一个执行环境: 局部变量、实例变量、self……既然这些都系都是绑定在对象上的名字,就可以简称为绑定(binding) 。
可以运行的代码由两部分组成:代码本身和一组绑定。
1
2
3
4
5
6
7
def my_method
x = "goodbye"
yield ( "cruel" )
end
x = "hello"
my_method { | y | " #{ x } , #{ y } word" } # => "hello, cruel world"
当创建块时会获取到局部绑定(比如上面的x),然后把块连同它自己的绑定传递给一个方法。
上面的例子,块的绑定包括一个名为x的变量。虽然在方法中定义了一个变量x,块看到的x也是在块定义时绑定的x,但时方法中的x对这个块来说是不可见的。基于这种特性,块被称为闭包(closure) 。
Scope
作用域是所有绑定寄居的场所。
程序会在三个地方关闭前一个作用域,同时打开一个新的作用域: 类定义、模块定义、方法。
这三个边界分别用class
、module
和def
关键字作为标志。每一个关键字都充当一个作用域门(Scope Gate) 。
1
2
3
4
5
6
7
8
9
10
11
12
v1 = 1
class MyClass # 作用域门: 进入class
v2 = 2
local_variables # => ["v2"]
def my_method # 作用域门: 进入def
v3 = 3
local_variables
end # 作用域门: 离开def
local_variables # => ["v2"]
end # => 作用域门: 离开class
在类和模块定义中的代码会立即执行。相反, 方法定义中的代码只有在方法被调用时执行。
Flattening the Scope
怎样让绑定穿越一个作用域门?
1
2
3
4
5
6
7
8
my_var = "success"
class MyClass
puts my_var
def my_method
puts my_var
end
end
上面的代码会报错。在进入另一个作用域时,局部变量会立即失效。如果能用方法代替class,就能在一个闭包中获得my_var的值,并把这个闭包传递给该方法。
用Class.new()
代替class
。用Module#define_method()
方法来代替def
:
1
2
3
4
5
6
7
8
9
my_var = "success"
MyClass = Class . new do
puts my_var
define_method :my_method do
puts my_var
end
end
使用方法来代替作用域门,那么可以让一个作用域看到另一个作用域中的变量,称为嵌套文法作用域(nested lexical scopes) ,简称扁平作用域(Flat Scope) 。
Shared Scope
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def define_methods
shared = 0
Kernel . send :define_method , :counter do
shared
end
Kernel . send :define_method , :inc do | x |
shared += x
end
end
define_methods
counter # => 0
inc ( 4 )
counter # => 4
instacne_eval()
1
2
3
4
5
6
7
8
9
10
11
class MyClass
def initialize
@v = 1
end
end
obj = MyClass . new
obj . instance_eval do
self # => #<MyClass:0x3340dc @v=1>
@v # => 1
end
在运行时,该块的接收者会成为self,因此它可以访问接收者的私有方法和实例变量。
1
2
3
v = 2
obj . instance_eval { @v = v }
obj . instance_eval { @v }
可以把传递给instance_eval()
方法的块称为上下文探针(Context Probe) ,它就像是一个深入到对象中的代码片段,对其进行操作。
Breaking Encapsulation
1
2
obj = Object . new
obj . instance_eval { @options = Object . new }
当然也可以创建一个完整的类,先在initialize()
方法中定义@options变量,然后创建该类的一个实例。
Clean Rooms
有时你会创建一个对象,仅仅是为了在其中执行块。这样的对象称为洁净室(Clean Room) 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CleanRoom
def complex_calculation
end
def do_something
end
end
clean_room = CleanRoom . new
clean_room . instance_eval do
if complex_calculation > 10
do_something
end
end
Callable Objects
从底层来看,使用块需要分两步。第一步,把代码打包备用;第二部,调用块(通过yield
语句)来执行代码。
有以下三种方法可以打包代码。
proc。 pro基本上就是一个有块转换成的对象。
lambda。 它是proc的近亲。
使用方法。
Proc Objects
一个Pro就是一个转换成对象的块,可以通过把块传给Prow.new()
方法来创建一个Proc。以后就可以用Proc#call()
方法来执行这个由块转换来的对象。这种技术称为延迟执行(Deferred Evaluation) 。
1
2
inc = Proc . new { | x | x + 1 }
inc . call ( 2 ) #=>3
也可以用lambda()
、proc()
创建Proc。
&操作符
把块转换成Proc
。
1
2
3
4
5
6
7
8
9
10
def my_method ( & the_proc )
the_proc
end
p = my_method { | name | "Hello, #{ name } !" }
puts p . class
puts p . call ( "Bill" )
=> Proc
Hello , Bill !
把Proc
转换成块。
1
2
3
4
5
6
7
8
de my_method ( greeting )
puts " #{ greeting } , #{ yield } "
end
my_proc = proc { "Bill" }
my_method ( "Hello" , & my_pro )
=> Hello , Bill !
Procs vs Lambdas
return
在lambda
中,return
仅仅表示从 这个lambda
中返回。
1
2
3
4
5
6
def double ( callable_object )
callable_object . call * 2
end
l = lambda { return 10 }
double ( l ) # => 20
在proc
中,return
从定义proc
的作用域中返回。
1
2
3
4
5
6
7
def another_double
p = Proc . new { return 10 }
result = p . call
return result * 2 # unreachable code!
end
another_double # => 10
参数数量
1
2
p = Proc . new { | a , b | [ a , b ] }
p . arity # => 2
lambda
参数数量不对时,会抛出ArgumentError
。而proc
会把传递进来的参数调整为自己期望的参数形式。
1
2
3
p = Proc . new { | a , b | [ a , b ] }
p . call ( 1 , 2 , 3 ) # => [1, 2]
p . call ( 1 ) # => [1, nil]
Quiz: A Better DSL
定义事件,并在每个事件前运行所有的setup。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
event "the sky is falling" do
@sky_height < 300
end
event "it's getting closer" do
@sky_height < @mountains_height
end
setup do
puts "Setting up sky"
@sky_height = 100
end
setup do
puts "Setting up mountains"
@mountains_height = 200
end
Soulution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
lambda {
setups = []
events = {}
Kernel . send :define_method , :event do | name , & block |
events [ name ] = block
end
Kernel . send :define_method , :setup do |& block |
setups << block
end
Kernel . send :define_method , :each_event do |& block |
events . each_pair do | name , event |
block . call name , event
end
end
Kernel . send :define_method , :each_setup do |& block |
setups . each do | setup |
block . call setup
end
end
} . call
Dir . glob ( '*events.rb' ) . each do | file |
load file
each_event do | name , event |
env = Object . new
each_setup do | setup |
env . instance_eval & setup
end
puts "ALERT: #{ name } " if env . instance_eval & event
end
end
IV. Class Definitions
Inside Class Definitions
1
2
3
4
5
result = class MyClass
self
end
result # => MyClass
在类(或模块)定义时,类本身充当了当前对象self的角色。
The Current Class
每当通过class关键字来打开一个类(或用module关键字打开一个模块),这个类就成为当前类。
使用class_eval()
修改当前类。
Module#class_eval()
方法(或者它的别名module_eval()
)会在一个已存在类的上下文执行一个块:
1
2
3
4
5
6
7
8
def add_method_to ( a_class )
a_class . class_eval do
def m ; 'Hello!' ; end
end
end
add_method_to String
"abc" . m # => 'Hello!'
Module#class_eval()
方法会修改self和当前类。
Object#instance_eval()
方法会修改self和接收者的eigenclass。
Class Instance Variables
在类定义的时候,self的角色由类本身担任,因此实例变量@my_var属于这个类。
类的实例变量不同于类的对象的实例变量。
1
2
3
4
5
6
7
8
9
10
11
12
class MyClass
@my_var = 1
def self . read ; @my_var ; end
def write ; @my_var = 2 ; end
def read ; @my_var ; end
end
obj = MyClass . new
obj . write
obj . read # => 2
MyClass . read # => 1
上面定义了两个实例变量,都叫@my_var,但它们分属于不同的作用域,并属于不同的对象。
MyClass的实例变量也叫类实例变量 。
类变量
可以被子类或类的实例所使用。
1
2
3
4
5
6
7
8
9
class C
@vv = 1
end
class D < C
def my_method ; @@v ; end
end
D . new . my_method # => 1
1
2
3
4
5
6
7
@@v = 1
class MyClass
@@v = 2
end
@@v # => 2
由于@@v定义于main的上下文,它属于main的类Object。MyClass集成自Object,因此它也共享了这个类变量。
Introducing Singleton Methods
1
2
3
4
5
6
7
8
9
str = "just a regular string"
def str . title?
self . upcase == self
end
str . title? # => false
str . methods . grep ( /title?/ ) # => ["title?"]
str . singleton_methods # => ["title?"]
只针对单个对象生效的方法,称为单件方法 。
The Truth About Class Methods
类只是对象,而类名只是常量。
1
2
an_object . a_method
AClass . a_class_method
类方法的实质就是:它们是一个类的单件方法。
单件方法的定义和类方法的定义是一样的。
1
2
def obj . a_singleton_method ; end
def MyClass . another_class_method ; end
在类定义中,由于self就是类本身,可以用self替换类名来定义类方法。
1
2
3
class MyClass
def self . yet_another_class_method ; end
end
1
2
def object . method
end
在上面的定义中,object可以是对象引用、常量类名或self。
Class Macros
Ruby对象并没有属性。如果希望由一些像属性的东西,就得定义两个拟态方法:一个读方法和一个写方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass
def my_attribute = ( value )
@my_attribute = value
end
def my_attribute
@my_attribute
end
end
obj = MyClass . new
obj . my_attribute = 'x'
obj . my_attribute # => 'x'
Module#attr_reader()
可以生成一个读方法,Module#attr_writer()
可以生成一个写方法,而Module#attr_accessor()
可以同时生成两者
1
2
3
class MyClass
attr_accessor :my_attribute
end
像attr_accessor()这样的方法称为类宏(Class Macro) 。虽然类宏看起来像关键字,但是它们只是普通的方法,只是可以用在类定义中而已。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Book
def title ; end
def lend_to ( name ); end
def self . deprecate ( old_method , new_method )
define_method ( old_method ) do |* args , & block |
warn "Warning: #{ old_method } () is deprecated. Use #{ new_method } ()."
send ( new_method , * args , & block )
end
end
deprecate :GetTitle , :title
deprecate :LEND_TO_USER , :lend_to
end
b = Book . new
b . LEND_TO_USER ( "Bill" )
=> Warning : LEND_TO_USER () is deprecated . Use lend_to () .
Eigenclasses Revealed
Object#class()把eigenclass隐藏起来。
Ruby由一种特殊的基于class关键字的语法,它可以让你进入该eigenclass的作用域:
1
2
3
4
5
6
obj = Object . new
eigenclass = classs << obj
self
end
eigenclass . class # => Class
每个eigenclass只有一个实例(这就是它们也称为单件类的原因),并且不能被继承。eigenclass是一个对象的单件方法的存活之所。
1
2
def obj . my_singleton_method ; end
eigenclass . instance_methods . grep ( /my_/ ) # => ["obj.my_singleton_method"]
Method Lookup Revisited
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class C
def a_method
'C#a_method()'
end
end
class D < C ; end
obj = D . new
obj . a_method # => 'C#a_method()'
class Object
def eigenclass
class << self ; self ; end
end
end
class << obj
def a_singleton_method
'obj#a_singleton_method()'
end
end
obj . eigenclass . superclass # => D
C . eigenclass # => #<Class:C>
D . eigenclass # => #<Class:D>
D . eigenclass . superclass # => #<Class:C>
C . eigenclass . superclass # => #<Class:Object>
class C
class << self
def a_class_method
'C.a_class_method()'
end
end
end
D . a_class_method # => "C.a_class_method()"