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非常非常像,除了它有个名字外...
好了,我可以去歇菜了,你们也好自为止...