MetaProgramming Chapter4

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值