Chinaunix首页 | 论坛 | 博客
  • 博客访问: 396614
  • 博文数量: 69
  • 博客积分: 1984
  • 博客等级: 上尉
  • 技术积分: 953
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-28 00:43
个人简介

学无所长,一事无成

文章分类

全部博文(69)

文章存档

2015年(19)

2014年(14)

2013年(9)

2012年(17)

2010年(10)

我的朋友

分类: Python/Ruby

2012-07-27 00:18:51

通过 Kernel#block_given?() 来询问当前方法调用是否包含块


  1. def a_method
  2.     return yield if block_given?
  3.     ’no block’
  4. end

  5. a_menthod # => ‘no block’
  6. a_method {“here’s a block”}


3.2 Ruby的#符号 p71

  1. module Kernel
  2.     def using (resource)
  3.         begin
  4.             yield
  5.         ensure
  6.             resource.dispose
  7.         end
  8.     end
  9. end

通过 ensure 语句可以保证 dispose 方法必被调用

3.3 闭包

可以运行的代码块由两部分组成:代码本身和一组绑定(局部变量,实例变量,self 等)

当定义一个块时,它会获取当时环境中的绑定,并且把它传给一个方法时,它会带着这些绑定一起进入该方法:


  1. def my_method
  2.     x=’goodbye’
  3.     yield(“cruel”)
  4. end

  5. x = ‘hello’
  6. my_method{|y| #{x},#{y} world} # =>”hello,cruel world”


变量绑定是在块定义时完成的,因此上例中的 x 只会绑定 ‘hello’。方法中的 x 对其是不可见得。

 作用域

块局部变量:块内部可以定义局部变量,在块执行结束后会消失。 

切换作用域

Ruby 中一旦进入一个新的作用域,原先的绑定就会被替换为一组新的绑定,原先的作用域就超出范围,完全不可见了。

一个对象调用同一对象中的其他方法,实例变量在调用过程中始终存在于作用域中。由此我们知道实例变量为什么要加个 @ 了,它同普通变量不同。

作用域门

程序会在三个地方关闭前一个作用域,同时打开一个新的作用域。

  • 类定义: class
  • 模块定义:module
  • 方法: def

只要程序见到以上三个关键字:class、module、def,就意味着进入了一个新的作用域,之前的作用域将不可见。

 

如上可见,普通变量是无法穿越作用域门的。要想在不同作用域间共享变量,有两个办法:全局变量($打头的变量) 跟 顶级实例变量(@打头的变量)。

但顶级实例变量在 self 发生改变时,会退出作用域。 

class/module 与 def 之间的区别:class/module 中代码会立即执行,def 中的代码只有被调用时才会执行。因此 反复调用同一方法时,我们得到的是不同的作用域,当然该作用域在方法调用结束时消失。 

扁平化作用域

class、module、def 是普通变量无法逾越的藩篱,如何让普通变量穿越作用域呢?哈,我们取消这三个关键字,用方法调用取代就可以了。

class MyClass 可以改写成 MyClass = Class.new do … end

module MyModule 可以改写成 MyModule = Module.new do … end

def my_method 可以改写成 define_method :my_method do … end

 

方法调用不会开启新的作用域,我们依然处于当前作用域中,所有变量自然可以为我所用。这就叫扁平化作用域。

 

共享作用域

如果想在一组方法间共享一个变量,而且不希望其它方法能够访问。那么我们可以将这一组方法放入一个扁平化作用域中,然后用 class、module或 def加以保护。


  1. def define_my_methods
  2.     shared = 0    
  3.     Kernel.send :define_method, :counter do
  4.         shared
  5.     end

  6.     Kernel.send :define_method, :inc do |x|
  7.         shared += x
  8.     end
  9. end

  10. define_my_methods

  11. counter # => 0
  12. in(4)
  13. counter # => 4



3.4 instance_eval()  p83


  1. v = 2
  2. obj.instance_eval {@v = v}
  3. obj.instance_eval {@v} # =>2


通过 instance_eval 可以将一个对象的的作用域暴露在当前作用域中,同时改变 self ;因此上面代码中我们既可以访问 v,也可以访问 @v。

Ruby 1.9 中引入了 instance_exec :同 instance_eval 功能相似,但它允许对块传入参数。 

3.5 可调用对象 p86

有三种方法可以打包代码

  • 使用 proc
  • 使用 lambda
  • 使用方法

Proc 对象

代码块不是对象,因此有必要将其打包,方法如下。


  1. inc = Proc.new {|x| x+1}
  2. inc.call(2) # =>3



Ruby 还提供了两个内核方法用于把块转换为 Proc:lambda() 和 proc()


  1. dec = lambda {|x| x-1}
  2. dec.class #=>proc
  3. dec.call(2) #=>

&操作符

块更像是方法的一个匿名参数,如果要给它命名,则可以使用 & 操作符


  1. def math(a,b)
  2.     yield(a,b)
  3. end

  4. def teach_math(a,b,&operation)
  5.     puts “let’s do the math :
  6.     puts math(a,b,&operation)
  7. end

  8. teach_match(2,3) {|x,y| x*y}

  9. => let’s do the math:
  10. 6



&operation 表示这是一个代码块,operation 表示这是一个 Proc 对象。


  1. def my_method(&the_proc)
  2.     the_proc
  3. end

  4. p = my_method {|name| “hello,#{name}!}

  5. puts p.class
  6. puts p.call(“bill”)

  7. =>Proc
  8. hello,


proc 与 lambda 的对比

  1. return 行为不同:块和proc 从其被定义 的作用域返回,往往是顶级作用域;lambda 和方法从其本身返回。因此 proc 最好不要用显式的 return 语句。
  2. 接受参数能力不同:proc 允许传入参数不规范,可多可少;lambda 必须同定义参数完全一致,否则会报错。

整体而言,lambda 更直观,更像是一个方法,因此很多 Ruby 使用者首选 lambda。

 

自己写了段小程序,做下测试:


  1. def test (&input)
  2.     puts input.call
  3.     puts yield #这里是执行的 block,并非 lambda ,所以 return 语句会报错,试图从顶级作用域 返回
  4. end
  5. l = lambda { return 'hello'}
  6. test(&l)


 Kernel#proc() 在 Ruby1.9 中,是 Proc.new() 的别名。我们还是只使用 Proc.new() 或者 lambda() 吧,省的搞混。

 

简洁 lambda


  1. p =>(x) { x + 1 } 等价于
  2. p = lambda {|x| x+1}


这只是一种实验性功能,还是暂时不要使用了。 

重访方法


  1. object = MyClass.new(1)
  2. m = object.method :my_method
  3. m.call


object.method 可以返回一个 Method 对象,可用 Method#call 对其调用。但此Method对象只在其所在的对象的作用域执行。

而 lambda 是个闭包,因此在其定义时的作用域中执行。

可以使用 Method#unbind() 方法将对象绑定断开,然后使用 bind 绑定到另外一个对象上(前后两个对象必须归属同一个类,否则报错)

这种技术似乎想不出其用途。

 

Method#to_proc() 可将 Method 对象转换为 Proc 对象。

define_method() 将块转换为方法。

3.6 编写一种领域专属语言

阅读(1409) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~