`
huangxiaoshi8896513
  • 浏览: 8587 次
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

理解Ruby中block的本质

 
阅读更多

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非常非常像,除了它有个名字外...

好了,我可以去歇菜了,你们也好自为止...

原作者:http://www.verydemo.com/demo_c119_i976.html

分享到:
评论

相关推荐

    深入理解Ruby中的代码块block特性

    听到代码块这个翻译,你或许会联想到类或者结构体,但block并不是这些东西,这里就带着大家来深入理解Ruby中的代码块block特性

    深入理解Ruby中的block概念

    Ruby 里的 block一般翻译成代码块,block 刚开始看上去有点奇怪,因为很多语言里面没有这样的东西。事实上它还不错。 First-class function and Higher-order function First-class function 和 Higher-order ...

    深入讲解Ruby中Block代码快的用法

    主要介绍了深入讲解Ruby中Block代码快的用法,block是Ruby学习进阶当中的重要知识,需要的朋友可以参考下

    Ruby 小白入门指南理解 Ruby 及其特点.txt

    安装完成后,你可以在命令行中输入 ruby -v 来检查 Ruby 是否成功安装以及安装的版本。 三、学习基础语法 变量和数据类型:Ruby 支持多种基本数据类型,包括整数、浮点数、字符串、数组、哈希等。你需要了解如何...

    Ruby中Block和迭代器的使用讲解

    主要介绍了Ruby中Block和迭代器的使用,是Ruby入门学习中的基础知识,需要的朋友可以参考下

    Ruby中的block、proc、lambda区别总结

    主要介绍了Ruby中的block、proc、lambda区别总结,本文讲解了yield 和 block call 的区别、block 和 proc、lambda 的区别、proc 和 lambda 的区别,需要的朋友可以参考下

    ruby中文资源大全

    在工作中,他希望有一种比 Perl 强大,比 Python 更面向对象的语言。从1993年2月,他开始设计一个全新的自己的语言,1994年12月发布了第一个 alpha版本,并且将这种新语言定名为Ruby(红宝石)。 本教程内含多部中文...

    Ruby中文文档.zip

    Ruby,一种简单快捷的面向对象(面向对象程序设计)脚本语言,在20世纪90年代由日本人松本行弘(Yukihiro Matsumoto)开发,遵守GPL协议和Ruby License。它的灵感与特性来自于 Perl、Smalltalk、Eiffel、Ada以及 Lisp ...

    Ruby中的block代码块学习教程

    在Ruby中,block并不是类,block可以被转化为Proc类中衍生出的对象,刚接触Ruby的话block是很难理解的一个点,接下来就为大家来总结Ruby中的block代码块学习教程

    Ruby中文帮助文档

    Ruby中文文档.CHM 方便ruby or rails学习.

    ruby中文教程,从基础到深入的让你学习ruby

    ruby中文教程,从基础到深入的让你学习ruby

    ruby中英文api

    ruby中英文api 适合于初学者。 希望能给大家带来一些帮助

    Ruby中文教程及相关源代码

    Ruby中文教程及相关源代码 Ruby中文教程及相关源代码 Ruby中文教程及相关源代码

    ruby 中文文档 必备资料

    ruby 中文文档 必备资料 一定要下哦

    Ruby完全自学手册 下

    希望借助于平易的讲解,让读者在学习的过程中,理解Ruby的编程思想,充分享受编程的乐趣,通过《Ruby完全自学手册》进入Ruby开发的殿堂。同时也希望能够与各位读者分享多年来积累的Ruby程序和网站开发的经验。 ...

    Ruby-HttpClient在Ruby中提供类似libwwwperlLWP的功能

    Http Client - 在Ruby中提供类似libwww-perl(LWP)的功能

Global site tag (gtag.js) - Google Analytics