第4章代码块blocks

  • 基础知识
  • 作用域:用代码块携带variables through scopes
  • 通过传递block给instance_eval方法来控制作用域。
  • 把block转换为Proc,lambda这样的可反复调用的对象。4.5

4.12基础

def a_method(a,b)
  a + yield(a,b)
end
p a_method(2,1){|x, y| x * y}  #=>4

4.3代码块是闭包Closures

定义一个块时,它会获取当前环境中的绑定bingding,如局部变量,实例变量,self等。当块被传入给方法时,它会带着这些绑定进入方法。

def my_method 
  x = "Goodbye"  #这里的x是方法内定义的,和代码块没有关系
  yield("Cruel")
end
x = "Hello"   #被代码块binding.
my_method{|y| "#{x}, #{y} world"}   #=>输出"Hello, Cruel world" 

代码块绑定了变量x进入了方法。

另外,可以在块内定义额外的绑定,比如在块内声明一个变量,但在块结束后,这个变量也就没了。

def just_yield
  yield
end
top = 1
just_yield do
  top += 1
  local_to_block = 1
end
p top      # => 2
p local_to_block # undefined local variable or method `local_to_block' 

4.31  Scope作用域

Kernel#local_varialbles : return the names of current local variables.

Ruby 的作用域是分开的,没有嵌套模式。不同于java。

全局变量$可以在任何作用域中访问。

@var 顶级实例变量,是顶级对象main的实例变量。在顶层声明。只要main对象扮演self的角色,就可以当全局变量用。但是,当其他对象成为self时候,顶级实例变量就落到scope外了。

4.32 Scope Gate

  • 类定义 ,class,
  • 模块定义,module
  • 方法, def。方法定义的代码不会立即执行。但类/模块定义的代码会立即执行。
当遇到到三个关键字,就是遇到Scope Gate,会改变scope.

4.33 Flattening the Scope

如何让两个作用域挤压在一起,可以共享各自的变量?使用方法调用

my_var = "You" #必须写在MyClass类定义上面,因为类定义的代码会立即执行。    
MyClass = Class.new do
  puts "#{my_var} in the class definition"
  define_method :my_method do
    puts "#{my_var} in the method"
  end
end
MyClass.new.my_method   #执行my_method方法

用Class.new方法替代class关键字,让define_method方法替代def关键字,如此就让类MyClass和方法my_method共享了局部变量my_var.

如果只让这几个方法共享某个变量,其他方法访问不了?把这些方法定义在一个flat scope中,这叫做share scope.

这里把counter和inc方法定义在了Kernel类中。

define_method是Module中的实例方法。

def define_methods
  shared = 0
  Kernel.define_method(:counter) do  
    shared
  end
  Kernel.define_method(:inc) do |x|
    shared += x
  end
end
p local_variables   #=> []
define_methods    
p counter    #=> 0
p inc(4)    #=> 4

4.34闭包小结

Ruby作用域都包含一组binding。不同的scope被Scoupe Gate分开(class,module,def关键字)

要想要让某个绑定穿越作用域,可以使用block. A block is a Enclose.闭包。当定义一个代码块时,它会捕捉当前环境中的binding,并带着它们四处流动。因此,你可以使用方法调用来代替Scope Gate, 用一个闭包获取当前的绑定,并把这个闭包传递给方法。

Class.new, Module.new, Module#define_method的用法叫Flattening the Scope.

4.4 instance_eval方法。

这是另一种混合“代码”和“绑定”的方法。 BasicObject#instance_eval.

把传递给instance_eval方法的代码块称为 Context Probe 环境上下文探针

用途:打破封装,查看对象内部细节,或者做单元测试用。

在接受者的环境上下文中判断这个代码块。为了设置环境,在运行代码块时,把self给接受者obj,这样代码块可以访问接受者的实例变量和私有方法。

Evaluates a string containing Ruby source code, or the given block, within the context of the receiver (obj).   In order to set the context, the variable self is set to obj while the code is executing,   giving the code access to obj's instance variables and private methods.

当获得一个块,接受者可以作为块的唯一参数。

When instance_eval is given a block, obj is also passed in as the block's only argument.

 

class Myclass

  def initialize
    @v = 1
  end
end
obj = Myclass.new
obj.instance_eval do |x|
  self  #<Myclass:0x00007fe35b809530 @v=1>
  x      #x就是self
  @v    # 1
end
v = 2
obj.instance_eval { p @v = v}   #=> 2

上面两行代码在同一扁平作用域中,所以可以访问局部变量。

instance_exec方法,区别就是可以传递除receiver之外的其他对象作为参数。

Clean room:创建一个只是为了在其中执行块的对象。可以用BasicObject的实例代替,因为它是白版类,几乎没有任何方法,很干净不会引起命名冲突。

4.5 Callable Objects

目的是代码块可以反复用,因此要把代码块打包成对象。

用3种方法把块变为可随时调用的对象。

  • proc
  • lambda
  • method
以后需要的话,就可以用Proc#call方法执行这个对象,这称为Deferred Evaluation.(延迟计算)

4.51 Proc对象

生成Procd对象:5个方法。

  1. Proc.new {|x| block}
  2. proc{|x| block} -> a_proc
  3. lambda {|x|block}
  4. ->(x){block} lambda的简写法
  5. 下一章讨论

&操作符:

  • 想要把block传递给其他方法或其他block
  • 想把block变为Proc对象。

在设定参数中,给参数加&,这个参数必须在最后的位置。

def math(x,y)
  yield(x,y)
end
def do_math(a,b,&operation)
  math(a,b,&operation)
end
p do_math(2,3){|x,y| x+y}
def my_method(&the_proc)
  the_proc
end
a = my_method{|name| "hello, #{name}"}
p a.call("tom")  #=>"hello, tom"

如果再想把Proc对象转变为代码块在方法中调用(yield),同样在参数中加&。

def my_method2(greeting)

  puts "#{greeting}, #{yield}"
end
my_proc = proc{"bill"}
my_method2("hello",&my_proc) #=>hello, bill

#my_method2("hello"){"bill"} ,结果一样。

4.52Proc,Lambda对比

Ruby程序员应当优先使用lambda,因为lambda更像一个方法。

1.return的区别。

在lambda中,return仅仅从这个lambda中返回。

def double(a)

  p a.call*2
end
x = lambda {return 10}
double(x)   #=>20

换成proc的话,不是从proc中返回,是从定义proc的作用域中返回(double方法),无效。

def double(a)
  p a.call*2
end
x = proc {return 10}
double(x)

2.参数区别,lambda要求参数数量必须匹配,否则会报错

4.53  Method Object(没太懂)

Method objects are created by Object#method, and are associated with a particular object (not just with a class). They may be used to invoke the method within the object, and as a block associated with an iterator. They may also be unbound from one object (creating an UnboundMethod) and bound to another.

method(sym) → method

looks up the named method as a receiver in obj, returning a Method object (or raising NameError). The Method object acts as a closure in obj's object instance, so instance variables and the value of self remain available.

class Demo
  def initialize(n)
    @iv = n
  end
  def hello()
    p "Hello, @iv = #{@iv}"
  end
end
k = Demo.new(99)
m = k.method(:hello)
m.call   #=> "Hello, @iv = 99"

4.6 Writing a Domain-Specific Language。

领域专属语言用来解决特定的问题。

Ruby是通用语言general-purpose language.

编写领域专属语言。Ruby的标准构建语言Rake不过是一个Ruby类库----内部领域专属语言

因为它在通用语言内部。相比之下那些拥有独立解析器的语言是外部领域专属语言。


元编程的2个定义:

  1. 编写在运行时操作语言构件的代码,本书基于这条定义。
  2. 设计一种领域专属语言,用它编写代码。

Kernel#load(filename, wrap=false) → true

Loads and executes the Ruby program in the file filename. If the filename does not resolve(分解seperate) to an absolute path, the file is searched for in the library directories listed in $:.

加载文件。

4.7 改良的DSL

小测验答案:

def setup(&block)
  @setups << block
end
def event(description, &block)
  @events << {:description => description, :condition => block}
end
@setups = []
@events = []
load "event.rb"
@events.each do |x|
  @setups.each do |y|
    y.call
  end
  if x[:condition].call  #b必须调用方法call. 原因见下⬇️
    puts "Alert:#{x[:description]}"
  else
    puts "Thank God"
  end
end
x[:condition] 其实是

# {:description=>"the sky ...", :condition=>#<Proc:0x00007ff1b。。。@event.rb:33>} 中的标黄部分,即一个Proc对象。

里面的块代码是“条件判断语句 ”,比如⬇️的标黄部分:

event "whoops...too late"  do
  @sky_height < 0
end

消除全局变量:

@count_s = 0  #这个是测试方法用了几次的测试代码。
@count_e = 0
lambda {
  setups = []
  events = []
  Kernel.define_method(:setup) do |&block|
    @count_s += 1
    setups << block
  end
  Kernel.define_method(:event) do |description, &block|
    @count_e += 1
    events << {:description => description, :condition => block}
  end
  Kernel.define_method(:each_setup) do
    setups.each do |setup|
      setup.call
    end
  end
  Kernel.define_method(:each_event) do |&block|
    events.each do |event|
      # event是什么?见⬇️ 
      # {:description=>"the sky ...或其他", :condition=>#<Proc:0x00007ff1b。。。@event.rb:33>}
      block.call(event)   #call:运行Proc对象并传入了一个参数event
    end
  end
}.call  #必须call完了才能共享作用域。
load "event.rb"
each_event do |x|
  each_setup
  if x[:condition].call
    puts "Alert:#{x[:description]}"
  else
    puts "Thank God"
  end
end

#黄色的部分是调用each_event方法同时传入的参数block,被转换为Proc对象。

p @count_e
p @count_s

添加clean house

p101

目标:让event之间不共享变量,setup和event可以共享变量。这是希望event之间应该保持独立。

each_event do |x|
  env = Object.new
  env.instance_eval(each_setup)
  if env.instance_eval &(x[:condition]) #=> 用&把Proc转换为block
    puts "Alert:#{x[:description]}"
  else
    puts "Thank God"
  end
end

设立一个洁净室,比如用白板类,新建对象,用这个对象的作用域执行代码(使用instance_eval,或instance_exec)

3-11 《Ruby元编程》第4章block块 3-12的更多相关文章

  1. 3-8《Ruby元编程》第二章对象模型

    <Ruby元编程> 第二章 对象模型 类定义揭秘inside class definitions: class关键字更像一个作用域操作符,核心作用是可以在里面随时定义方法. [].meth ...

  2. C++11用于元编程的类别属性

    [C++11用于元编程的类别属性] 许多算法能作用在不同的数据类别; C++ 模板支持泛型,这使得代码能更紧凑和有用.然而,算法经常会需要目前作用的数据类别的信息.这种信息可以通过类别属性 (type ...

  3. C++11模版元编程

    1.概述 模版元编程(template metaprogram)是C++中最复杂也是威力最强大的编程范式,它是一种可以创建和操纵程序的程序.模版元编程完全不同于普通的运行期程序,它很独特,因为模版元程 ...

  4. C++11模版元编程的应用

    1.概述 关于C++11模板元的基本用法和常用技巧,我在程序员2015年2月B<C++11模版元编程>一文(后称前文)中已经做了详细地介绍,那么C++11模版元编程用来解决什么实际问题呢, ...

  5. Ruby元编程:单元测试框架如何找到测试用例

    前几天看了Google Testing Blog上的一篇文章讲到C++因为没有反射机制,所以如何注册测试用例就成了一件需要各显神通的事情.从我的经验来看,无论是Google的GTest还是微软的LTM ...

  6. Ruby元编程:动态添加类属性及其实际应用

    上个星期测试道的Monkey老师和我聊到测试用例参数过多的问题,其实这样的问题在我这里也同样经历过.比如我的测试用例必须面对不同的测试环境,每个环境有无数的参数,开发的最初阶段,因为参数少,所以就放在 ...

  7. 201707《Ruby元编程》

    元编程不过是编程--经典必读 作用域(绑定) 打破作用域门的方式 对象模型图 七条规则 法术手册 作用域(绑定) 改变作用域的关键字, 分别是module,class和def.我们称为作用域的门(sc ...

  8. ruby 元编程

    一 对象模型 kernel Module Kernel.private_instance_methods.grep(/^pr/)   private method 1 如果一个方法接收者不是你自己,一 ...

  9. 3-18/19 (自我练习)30多个《Ruby元编程》的spell(pattern)小例子。

    Spell,也称pattern,idiom # Around Alias:从一个重新定义的方法中调用原始的,被重命名的版本. # old_reverse是未改变的原始方法,reverse/new_re ...

随机推荐

  1. 20145316许心远《网络对抗》MSF基础应用

    20145316许心远<网络对抗>MSF基础应用 实验后回答问题 用自己的话解释什么是exploit,payload,encode. exploit:顾名思义就是攻击嘛,因为是个动词,所以 ...

  2. json-lib-2.4-jdk15.jar所需全部JAR包.rar java jsoup解析开彩网api接口json数据实例

    json-lib-2.4-jdk15.jar所需全部JAR包.rar  java jsoup解析开彩网api接口json数据实例 json-lib-2.4-jdk15.jar所需全部JAR包.rar  ...

  3. Python装饰器的高级用法(翻译)

    原文地址 https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function 介绍 我写这篇 ...

  4. Python入门之实现简单的购物车功能

    Talk is cheap,Let's do this! product_list = [ ['Iphone7 Plus', 6500], ['Iphone8 ', 8200], ['MacBook ...

  5. List集合实现简易学生管理

    题目: 代码: package org.wlgzs; import java.util.ArrayList; import java.util.List; import java.util.Scann ...

  6. luoguP2572 [SCOI2010]序列操作

    题目&&链接 反正数据都是一样的,luogu比较友好 luogu bzoj lxhgww最近收到了一个01序列,序列里面包含了n个数,这些数要么是0,要么是1,现在对于这个序列有五种变 ...

  7. POJ2528 Mayor's posters(线段树&区间更新+离散化)题解

    题意:给一个区间,表示这个区间贴了一张海报,后贴的会覆盖前面的,问最后能看到几张海报. 思路: 之前就不会离散化,先讲一下离散化:这里离散化的原理是:先把每个端点值都放到一个数组中并除重+排序,我们就 ...

  8. POJ 3630 Phone List(字符串前缀重复)题解

    Description Given a list of phone numbers, determine if it is consistent in the sense that no number ...

  9. js ajax跨域

    一般情况后台返回... 也就是说,无论数据本身是什么数据类型,数据,对象,都是以字符串形式返回的. 如何把字符串化成相应对象. 如: var s='{"left":100}' co ...

  10. C# String.Join 与 StringBuilder 对比,谁更快

    String.Join 文档      StringBuilder 文档 这两天刷 Leedcode 做到一道 String 的题时突然想到这俩对比的问题,于是查了一下资料并简单对比了一下. 首先对于 ...