MetaProgramming Chapter4
Ruby 元编程 第四章
该文档包含Ruby方法的介绍。
阅读文档,你将学到:
- 代码块的基础知识
- 作用域基础知识
- 代码块携带变量穿越作用域
- 通过block给instance_eval方法来控制作用域
- 如何把块转换为proc/lambda这样的可调用对象
代码块基础知识
NOTE:
1. 定义方式: do…end/{}, 且只有在调用方法时才能调用块;
2. 传递方式: 直接传给方法
3. 调用方式: yield关键字调用块
4. 询问当前方法调用是否包含块: Kernel#block_given?
def aa
return "block exist!" if block_given?
"block blank!"
end
aa # => "block blank!"
aa {} # => "block exist!"
aa do end # => "block exist!"
INFO: 在使用yield前,需调用block_given? 方法,否则,可能导致运行时错误
def test_no_yield
puts "没有代码块,但是执行了代码块,就会报错"
yield
end
test_no_yield
# => 没有代码块,但是执行了代码块,就会报错
# => LocalJumpError: no block given (yield)
作用域
The main point about blocks is that they are all inclusive and come ready to run. They contain both the code and a set of bindings.
作用域门Scope Gate
- class
- module
- def
v1 = 1
class MyClass # SCOPE GATE: entering class
v2 = 2
local_variables # => ["v2"]
def my_method # SCOPE GATE: entering def
v3 = 3 # => [:v3]
local_variables
end # SCOPE GATE: leaving def
local_variables # => ["v2"]
end # SCOPE GATE: leaving class
obj = MyClass.new
obj.my_method # => [:v3]
local_variables # => [:v1, :obj]
闭包
NOTE:
- Kernel#local_variables 查看当前binding–局部变量
- 在控制台打印local_variables 查看输入的变量
class Scope
a = 1
local_variables
def scope_test
b = 2
local_variables
end
local_variables
end
NOTE:
- ruby的作用域之间是独立的。一旦进入新的作用域,外部的binding都不可见
绑定对象
class A
def a_method1
@a = "A的实例变量,在a_method1方法中,binding为方法内的作用域"
binding
end
end
a = A.new.a_method1
eval "@a", a
NOTE: TOPLEVEL_BINDING:顶级作用域, 通过 self 输出该作用域绑定的对象
class B
def b_method1
eval "self", TOPLEVEL_BINDING
end
end
b = B.new.b_method1
Pry
NOTE: Pry定义了Object#pry方法,在规定的对象作用域中打开一个交互。
在当前绑定对象上调用pry方法,实现调试功能
require "pry";binding.pry
self
NOTE: C#c_method1方法中调用pry方法,进入当前作用域
class C
def c_method1
@c = "C的实例变量"
require "pry";binding.pry
puts @c
end
end
c = C.new
c.c_method1
self
全局变量和实例变量
NOTE:
- 全局变量,最好不使用,$
- 顶级实例变量,顶级对象main的实例变量@
def scope_a
$var = "全局变量"
end
def scope_b
$var
end
@var = "外面的顶级实例变量"
def scope_c
@var = "c的顶级实例变量"
end
def scope_d
@var
end
scope_b
scope_c
scope_a
scope_b
INFO: 可以尝试在调用scope_a之前,调用scope_b,查看效果
扁平化作用域
NOTE:两个作用域共享各自变量,则称为扁平作用域
NOTE:
要想共享变量,则打破作用域门,
- class -> Class.new
- module -> Module.new
- def -> define_method
my_var = "Success"
MyClass = Class.new do
"#{my_var} in the class definition"
define_method :my_method do
"#{my_var} in the method"
end
end
MyClass.new.my_method
NOTE:使用场景: 希望在一组方法间共享一个变量,但又不希望其他方法访问此变量,
就可以把这下方法定义在扁平作用域内
def define_methods
share = "初始化变量"
define_method :share_method_a do
share
end
define_method :share_method_b do
share = share + "添加点东西,表示它共用"
end
end
define_methods
share_method_a
share_method_b
share_method_a
instance_eval
BasicObject#instance_eval: Context Probe
NOTE:运行时,代码块的接收者会成为self,因此可以访问接收者的私有方法好实例变量。
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj.instance_eval do
sel # => #<MyClass:0x3340dc @v=1>
@v # => 1
end
v = 3
obj.instance_eval { @v = v }
obj.instance_eval { @v } # => 3
instance_exec
BasicObject#instance_exec: 可以对block传入参数
class D
def twisted_method
@y = 2
C.new.instance_eval {|y| "@x: #{@x}, @y: #{y}" }
end
end
D.new.twisted_method # => "@x: 1, @y: "
INFO: @y为D的实例变量,绑定当前的self,故该block和twisted_method非处于同一扁平作用域
class D
def twisted_method
@y = 2
C.new.instance_exec(@y) {|y| "@x: #{@x}, @y: #{y}" }
end
end
D.new.twisted_method # => "@x: 1, @y: 2"
可调用对象:打包代码
Proc对象
将代码块转化为Proc对象的方法:
- Proc.new
- lambda
- &操作符
NOTE:
块不是对象,需要Proc类来存储block,以供以后执行
NOTE:
创建proc的方式:
- Kernel#proc & Kernel#lambda
Proc类
inc = Proc.new {|x| x + 1 }
inc.call(2) # => 3
lambda
dec = lambda {|x| x - 1 }
dec.class # => Proc
dec.call(2) # => 1
p = ->(x) { x + 1 }
p = lambda {|x| x + 1 }
&操作符
NOTE:
- 把代码块传递给另一个方法
- 把代码块转换成Proc
def math(a, b)
yield(a, b)
end
def do_math(a, b, &operation)
math(a, b, &operation)
end
do_math(2, 3) {|x, y| x * y} # => 6
NOTE: &:定义一个proc对象,block与Proc对象的转换
def my_method(&the_proc)
the_proc
end
p = my_method {|name| "Hello, #{name}!" }
p.class # => Proc
p.call("Bill") # => "Hello, Bill!"
def my_method(greeting)
"#{greeting}, #{yield}!"
end
my_proc = proc { "Bill" }
my_method("Hello", &my_proc)
Lambda与Proc差别
NOTE:
- return作用域
- 代码块参数
使用方法
- 方法与对象的转换
- 可调用对象
class A
$var = "全局变量"
@@v = "类变量"
@v = "类实例变量,A.m"
def self.m
puts $var
puts @@v
puts @v
local_variables
end
def m1
puts $var
puts @@v
puts @v
local_variables
end
end
def add_method_to(a_class)
a_class.class_eval do
def m2
a = "class 外的变量,使用扁平作用域"
"#{self.class}#m #{a}"
$var
end
def m3
@@v
end
def m4
@v
end
end
end
a = A
add_method_to(a)
a.instance_methods(false)
aa = A.new
aa.m1
aa.m2
aa.m3
aa.m4