理解Ruby中block的本质_Ruby_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > Ruby > 理解Ruby中block的本质

理解Ruby中block的本质

 2011/10/18 6:11:25  ruby_windy  http://ruby-windy.iteye.com  我要评论(0)
  • 摘要:Ruby非常特色的特性有两点:Module优雅地解决多继承问题Block块调用虽然这两个特性均不是Ruby原创,但显然是它将这两个特性发挥到很恰到好处,害的最近的C#也在改进支持它们.然而,正是这两个特性,使得RubyBeginner经常迷惑不解.这也是我在学习过程中经常会遇到的问题,我想就将Block解牛的过程列出来,也算是对自己的过程作一个记录吧.前言在Ruby,所谓的"Block"有多种,而Block在计算机科学理论中被称为"过程",(哇
  • 标签:Ruby 理解
Ruby非常特色的特性有两点:
  • Module 优雅地解决多继承问题
  • Block 块调用

虽然这两个特性均不是Ruby原创,但显然是它将这两个特性发挥到很恰到好处,害的最近的C#也在改进支持它们.

然而,正是这两个特性,使得RubyBeginner经常迷惑不解.这也是我在学习过程中经常会遇到的问题,我想就将Block解牛的过程列出来,也算是对自己的过程作一个记录吧.

前言
在Ruby,所谓的"Block"有多种,而Block在计算机科学理论中被称为"过程",(哇,就是当年研究Pascal中的关键字closures)" , Block在Ruby中有几种称谓,Blocks, Procs 和 lambdas. 其中表现行为上具有些许不同,也是造成Rubyers误解的直接原因.
没办法,ruby设计哲学之一就是,与现实世界类似,一个问题可以有几种不同的方法解决.
那么,为了更少的编码,为了更"RubyLike"的编码,我们开始吧.

开始
1. 它应该是什么?
(如果你还没弄清楚Block是怎么回事,建议回去啃RubyProgramming第二版那本镐书先)
我们先从一个需求开始,
我们看一个例子,数组支持排序,供我们使用.
那么看一下用法:
#按长短排序
["p","bc","de"].sort do |i,j|
  i.size <=> j.size
end
#按字母顺序
["p","bc","de"].sort do |i,j|
  i <=> j
end


看得出,使用block使得排序这个sort方法很强大.也很易理解.

如果对C很熟悉,也许有一种感觉,很像C的函数指针呀,确实,我开始的时候也有此感觉,不过细想,C的函数指针使用真的风险很大,即复杂又要各种类型转换.

如果对函数式编程有所了解的话,可以感觉到这个block又像所谓的"高阶函数"参数,嗯,确实,其实大部分时候就把它当作方法的参数即可,只可以这个参数不是一个表达式,而是一份完整的代码.


2. 有哪些形式?
(引子,听说过block,Proc,lamba,Method了吧?)
接下来看看它们的形式:

block:
block作为ruby最经常使用的语法之一,也是最"RubyLike"的,其用法以上简单介绍了.
我们再看到如何实现自己方法的block.

def method_with_block
  yield
end
method_with_block do puts 'call me' end


so easy, 使用yield这个关键字即可支持所谓的方法参数,如果想支持带block参数,也可以:

def method_with_block_parameter
  n = 'windy'
  yield(n)
end
method_with_block do |i| puts 'call me' + i end


Proc:
作为block的同胞,它是在ruby发展过程中出现的,它出现的目的,很简单,能复用block.
看一个需求:
帮我把两个数组arr1,arr2都按长短排序,我们利用Proc可以这么写:
size_code = Proc.new do |i,j|
  i.size <=> j.size
end
arr1.sort(&size_code)
arr2.sort(&size_code)

很简单嘛,可以注意到它的参数带了&这个符号,&的目的也很简单,告诉ruby解释器,'哟,这个不是一般的参数,是"高阶函数"'
另外,细心的人可以发现,我写Proc总是P大写的,而block总是小写,这是为什么呢?
1. Proc实际是Ruby内置的一个类,而block只是我们称谓代码块用的简写.
2. 一个Proc实例可以当作方法参数传递,而block更像是匿名的Proc

说到这里,block跟Proc基本上区别已经很清晰了,我们再补充一些关键的特性:
1. block与Proc跳出代码都使用next(而不是return,接下来要说的lambda正好与此相反)
2. block与Proc都只能在方法中接受一次,也即方法不能获得多个block或Proc(不用担心,松本行弘也说过,你用不着使用两个block) (另外,其实,你可以传递一个hash或数组,得到两个Proc实例)
3. 它们都有返回值,即最后一句代码的返回值.(我一直对ruby所有表达式的返回值没有弄的十分清楚,有弄清楚的同学欢迎讨论下)

lambda:
使用lambda让ruby更加的有趣起来,用老外的话叫funny了,也使我们更加困惑了,弄这么多形式干嘛?
先说下lambda本身,我们知道ruby是揉合了众多语言的特点,lambda不例外的是从lisp本身得到的命名.lambda念出来大家都好像听过,嗯,就是希腊的字母λ嘛.(第十一个)
我想说的就是它只是一个关键字,为了表达某种意思,产生的原因就是ruby语言之神设计的.(没啥特别的...)

来历我们清楚了,看下使用:
以上面的Proc的例子为准,我们继续写一个lambda的例子:

size_code_lambda = lambda do |i,j|
  i.size <=> j.size
end

arr1.sort(&size_code_lambda)
arr2.sort(&size_code_lambda)

嗯,没区别?

嗯,这里真的没区别,靠,那要它做甚.
其实你可以使用 size_code_lambda.class 看一下,发现它也是Proc对象?
嗯,怎么回事,其实你使用&传递的时候,ruby帮你全部转换成Proc了,不过此Proc对象非彼Proc对象,是有区别的.

我们再看一个例子:
一不小心我没弄清楚sort能传几个参数进去.
我这么写了(如果,多了一个k):
size_code_lambda = lambda do |i,j,k|
  i.size <=> j.size
end
arr1.sort(&size_code_lambda)

嗯,报异常了.
你把用Proc的代码跑一下,嗯? 没问题.
好,我们总结第一个区别:
1. lambda开始帮我检查代码传递参数了,而Proc不帮忙.
那既然Proc不检查,它传什么给我? 我们可以很快验证下:
size_code = Proc.new do |i,j,k|
  puts k,k.class
  i.size <=> j.size
end
arr1.sort(&size_code)
# nil, NilClass

嗯,空值.没错.(跟你预期一样吧?)

再继续看,我们有时候想从代码块中返回出来继续执行怎么办?
试着return吧:
一个例子
def methods(&code)
  code.call
  puts 'methods end'
end

a_proc = Proc.new { puts 'call proc' ; return ; }

a_lambda = lambda { puts 'call lambda' ; return ; }
methods(&a_proc) # => unexpected return (LocalJumpError)
methods(&a_lambda) # => methods end



输出截然不同啊,那么我们有第2个区别了:
2. lambda中返回使用return,而Proc/block中不能.
那么使用什么返回,看过那本镐书后应该知道,使用next即可.

那么加深一点印象,提个问题,如何Proc使用return为什么会返回LocalJumpError?
其实很简单,Proc保留了当时定义代码块的上下文环境,即闭包,这里返回LocalJumpError的原因是我们定义的上下文无法再次return了
换句话说,Proc中return其实是直接return到proc的上一层环境了.
那其实这里有个技巧:

使用Proc可以实现高阶跳转

如:
def go
  a = Proc.new do 
    puts 'proc'
    return
  end
  
  methods(&a)
  
  puts 'end go'
end

def methods(&a)
  puts 'methods'
  a.call
  puts 'end methods'
end

go
# =>methods
# proc

看懂了你算完全明白了Proc与lambda的这点区别了.

好了,区别总算讲完了,那我们应该怎么去使用呢?
1. lambda更像是一个完整的方法,只不过是匿名的,它的参数会被"神"检查下,它return的时候就是从自己的"方法"返回.
2. Proc只是一个过程,无依无靠,参数不对的时候只是被遗弃或随机捡一个nil送给你,return的时候就从原始父环境返回.

根据ruby发展过程,lambda比block/Proc更晚一点出来,lambda语法当然会比proc更好一些(从不要让我惊讶的角度),但proc没有消失的原因,一方面是兼容性,另一方面也与它的"魔法"有一点关系.
所以,使用的话,block应该优先被使用,它更简洁.
如果需要进行对象传递的话,建议更多使用lambda,它更少让你惊讶,还可以帮你检查参数,何乐而不为呢?
如果遇到Proc的高阶跳转用法了,能明白就好.

看到这里,基本上这篇总结的营养就没啥的了,你也会感觉都明白于心了.但是,我还是想把实际情况彰明一下:
lambda其实也不过是Proc的一种实例...(ruby学习深度过高的原因就在这里了,有兴趣的可以研究或跟我探讨一下)

最后最后,lambda既然如此像方法(Method),就简单说一下Method:
Method也是ruby中的一个类,它的名字已经告诉我们一切了,见一个例子:
def a
  puts 'a'
end

puts method(:a).class

method(:a).call
# => Method
# a


Method类与lambda非常非常像,除了它有个名字外...

好了,我可以去歇菜了,你们也好自为止...
发表评论
用户名: 匿名