Ruby 的 Block 块是它的关键特色之一,用块能够写出简明且高度可重用的算法。即使没有别的用处,它至少消弱了人们对循环敬畏的态度。这个概念在其他语言和理论中还被称为:
- lambda 函数
- 匿名函数
- 闭包(参见 Java 7 中 lambda 函数所使用的名称)
这是个十分令人迷惑的词汇,因为闭包这个词汇还指对代码作用域的捕获。而块则不需要捕获这个作用域——例如下面的代码
x = lambda {|x,y| x + y}
没有使用自由变量(没有绑定的变量;参数列表中正式声明 x 和 y),因此无须创建一个闭包。
块在其他语言中有很多种多样的表现形式,有的简洁有的冗长。比如对 Ruby 影响深远的LISP语言,所使用的块语法为:
`(lambda (arg) “hello world”)```
对 Ruby 的设计产生影响的另一种语言Smalltalk,采用方括号来简洁地表达语法:
[arg| ^"hello world"]
Ruby 中,块的最方便也最常使用的语法是作为函数的参数。它允许简单地在函数名后面添加一个用 do/end 或者花括号{ / }包围的代码块。例如:
5.times {|x| puts x}
这非常的方便,同时也产生了Builder这样的习惯性用法。Builder 可以通过嵌套的块来很容易地创建分层的数据结构。(提示:就在一月下旬 InfoQ 即将发表一篇详细描述如何在 Ruby 中创建 Builder 的文章)。
不过,还有一个问题:要传递一个以上的块给函数或方法就没那么简单了。它可以实现,但不能用这么短的语法,得使用 Proc.new {} 或lambda {}
来创建块。虽然还不至于恐怖,但这样会使代码冗长,而且还引入了一些不受欢迎的词汇把代码搞得凌乱不堪。(注意: Proc.new {}
和 lambda {}也有些微妙的不同
,但本文不关注它们)。
在特定情况下可能有变通的方法。例如,如果一个 API 调用需要多个块,辅助函数就会嵌入到类中,这样就产生了两个作用:a) 辅助了块 b) 带有貌似命名参数的负作用:
find (predicate {|x,y| x < y}, predicate{|x,y| x > 20})
其中 predicate
函数仅仅是:
def predicate(&b)<br></br> b <br></br>end
它用来返回这个块。不论这是否合适或者不依赖于特定情况。在这种情况下,下面的代码——毋庸置疑地——更能表达清楚,也能起到相同的作用。> find (lambda{|x,y| x < y}, lambda {|x,y| x > 20})
为什么呢?因为 lambda 泄露了实现它的细节——若带有一个块参数,就不需要额外的关键词。predicate 的解决方案对代码做了注解,并产生了 lambda。需要明确的是,这只是变通的方法。
现在,Ruby 1.9引入了一个新的、更简洁的语法来创建lambda**** 函数:
x = ->{puts “Hello Lambda”}
新的语法更加简短,还抛弃了那个不知所云的术语 lambda。需要明确的是,这是个语法糖衣。不过它的确有助于写出可读性非常好的 API 代码。其中一些 API 可以被称为“内部 DSLs”,尽管它们的定义都很模糊。出于这些,新的 lambda 定义帮我们摆脱了那个夹在要么是纯领域要么是问题确定的代码中间的晦涩的术语“lambda”。
Sidu Ponnappa 报告了 1.9 中另外一个语法变化:
在 Ruby 1.9.0 中,在一个块内显式调用另一个块。在上一篇帖子中我没有提到这个方法,因为解析器一遇到|*args, &block|时就会工作失常。代码如下:[…]
<span color="#013694"><span color="#013694"><span color="#000000"><span color="#013694"><span color="#000000">class SandBox<br></br> def abc(*args)<br></br> yield(*args)<br></br> end<br></br>define_method :xyz do<br></br> |*args, &block|<br></br> block.call(*args)<br></br> end<br></br>end<br></br>SandBox.new.abc(1,2,3){|*args| p args} # => [1, 2, 3]</span></span></span></span></span>
这段代码在 Ruby 1.8.x 下无法运行——它在解析阶段就失败了:
<span color="#013694"><span><span><span color="#013694"><span color="#000000">benchmark3.rb:8: syntax error, unexpected ',', expecting '|' <br></br>define_method :xyz do |*args, &block|<br></br> ^ <br></br>benchmark3.rb:11: syntax error, unexpected kEND, expecting $end</span></span></span></span></span>
在 Ruby 1.9 中,它可以正常运行。
1.9 的另外一个变化就是修复了一个早就发现的问题: 现在块参数是局部的了。看这段代码:
foo = "Outer Scope"<br></br>[1,2,3].each{|foo|<br></br> foo = "I'm not local to this block" <br></br>}<br></br>puts foo
在 1.8 中,这段代码会输出"I’m not local to this block",而在 1.9 中,输出为"Outer Scope"。简而言之,现在块像我们期望的那样工作了:块参数遮住了块外的同名变量。(我们先来问自己一个问题:“如何访问外部域的变量”。你不能—— 仅为块参数选择一个不同的名字)。
你怎么看 Ruby 1.9 中 lambda 和块的变化?它们涉及了我们一直关注的问题了吗?还有没有遗留的问题?
评论