深入理解Ruby中的block概念

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

First-class function 和 Higher-order function 是函数式编程语言里面的概念,听起来好像很高端的样子,其实很很简单的。

First-class functions 是指在某些语言里,函数是一等公民,可以把函数当做参数传递,
可以返回一个函数,可以把函数赋值个一个变量等等,反正就是正常值能做的事函数都能做。JavaScript 就是这样的。举个例子(下面的所有例子里,当我提到
JavaScript 时,示例代码都用的 CoffeeScript):

greet = (name) ->
 return -> console.log "Hello, #{name}"

greetToMike = greet("Mike")
greetToMike() # => 输出 "Hello, Mike"
a = greetToMike
a() # => 输出 "Hello, Mike"

在上面的第四行里,greet("Mike") 返回了一个函数,所以第五行里才可以调用 greetToMike()输出"Hello, Mike"。第六行把一个函数赋值给了a,所以第七行就可以调用这个函数了。

higher-order function 一般翻译成高阶函数,是指接受函数做参数或者返回函数的函数。
举个非常常用的例子(用 JavaScript):

a = [ "a", "b", "c", "d" ]
a.map((x) -> x + '!') #=> ["a!", "b!", "c!", "d!"]

上面例子里 map 就接受了一个匿名函数作为参数。Array.prototype里的很多方法,比如reduce, filter,every, some 等等都是高阶函数,因为他们都接受函数作为参数。

高阶函数非常强大,表达力很强,可以避免大量重复代码。总的来说,它就是个好东西。
Block 的本质

先来看一组 Ruby 和 CoffeeScript 代码的对比。

a = [ "a", "b", "c", "d" ]
a.map { |x| x + "!" } # => ["a!", "b!", "c!", "d!"]
a.reduce { |acc, x| acc + x} # => "abcd"

a = [ "a", "b", "c", "d" ]
a.map((x) -> x + '!') # => ["a!", "b!", "c!", "d!"]
a.reduce((acc, x) -> acc + x) # => "abcd"

这两组代码真的看起来超级像。我觉得这也暴露了 Ruby 的 block 的本质:高阶函数的函数参数的变体。

JavaScript 里面的map 函数接受一个函数作为参数,但是 Ruby 里的 map 却接受一个
block 作为参数。

其实 matz 早在一本书里《松本行弘的程序世界》里说了:

代码如下:

最终来看,块到底是什么?
    ...
    块也可以看作只是高阶函数的一种特殊形式的语法。
    ...
    高阶函数和块的本质一样
    ...

在 Ruby 里,函数不是一等公民,没有 first-class functions。但是在 Ruby
里怎样使用高阶函数呢?答案就是使用 block。可以直接用 block,也可以用 lambda
或者 proc 把 block 转换成 Proc 类的实例用。

我发现在 Ruby 里使用 block 时,几乎所有的情况下都可以用 JavaScript
的高阶函数替代。

Enumerable 模块里的所有方法都是典型的例子。事实上确实存在 JavaScript 版
的 Enumerable,比如 Prototype.js 就有个 Enumerable,用起来跟 Ruby版的几乎一样的。当然它是通过高阶函数实现的。
与高阶函数有何不同

除了语法上看上去有点不同外,有非常重要的两点。
控制流操作

在 block 里面可以用 break, next 等等这些在一般的循环里才有的控制流操作,这些
在高阶函数里是用不了的。比如你可以试试在 JavaScript 里用 forEach 而不用循环
实现个take_while 函数,真是相当别扭的。比如之前 cnode 上就有人发帖问:nodejs的forEach不支持break吗?,其实这个帖子下面回复用 return 的基本上都是错的,
some 和 every 这样利用 短路求值 的特点确实可以 hack 一下,但是明显不自然而且大大增加了别人理解代码的难度。

从这一点来看 block 确实还不错的。
只有一个函数参数的高阶函数

Ruby 里一个方法只能接受一个 block 作为参数,大概就是类似于只有一个函数参数的高阶
函数。看起来好像是受到限制了。其实那本《松本行弘的程序世界》对此也有点解释。
大概是说了一个调查,在倾向于使用高阶函数的 OCaml 的标准库中,94%
的高阶函数只有一个函数参数。所以说这点限制不是什么问题。就我自己的体验来说,在 JavaScript 里,还从没用到需要两个函数参数的高阶函数。
未说明的

嗯,这篇文章看起来有点太长了,所以我不打算写下去了。其实还有一些重要的地方没说。比如
Block 其实可以作为闭包用的。Ruby 里用def定义方法时有点悲剧的,因为它不是闭包,接触
不到它外面的变量。

name = "mike"
def greet
 puts "hello, #{name}"
end
hello # => in `greet': undefined local variable or method `name' for main:Object (NameError)

但是用 block 就可以了

name = "mike"
define_method(:greet) do
 puts "hello, #{name}"
end
greet # => "hello, mike"

用 JavaScript 就根本不存在问题。

name = "mike"
greet = -> console.log "hello, #{name}"
greet() # => "hello, mike"

同理还有class 和 module 关键字都会创建新的作用域而在里面接触不到外面的变量,
也可以用 block 解决。

还有那个 proc 和 lambda 的区别。其实我一直不理解为什么会有人不用lambda
而跑去用 proc,明显 proc 的 return 行为太不符合常识了。但是到头来却发现
block 的行为跟 proc 创建的对象的行为是一样的,比如

def hello
 (1..10).each { |e| return e}
 return "hello"
end
hello # => 1

这感觉真是有点悲催。
结语

说了这么多,就是因为在 Ruby 里面函数不是一等公民,又想获得函数式编程的便利。

(0)

相关推荐

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

    我们来简单地描述Ruby的一个独特特性.Block,一种可以和方法调用相关联的代码块,几乎就像参数一样.这是一个不可思议的功能强大的特性. 可以用Block实现回调(但它比Java的匿名内部(anonymous inner)类更简单),传递一组代码(但它远比c的函数指针灵活),以及实现迭代器. Block只是在花括号或者do...end之间的一组代码. {puts "Hello"} #this is a block do ### club.enroll(person) #and so

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

    Block 定义 some_array.each { |value| puts value + 3 } sum = 0 other_array.each do |value| sum += value puts value / sum end A block is somewhat like the body of an anonymous method Block can take parameters Block 只有被 method 调用时才会起作用,如果 method 中有参数,bloc

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

    在规则引擎中,Ruby 的闭包使用特别频繁,而且有 block,Proc和 lambda 等后几种形式的用法,很让人困惑.为了深入理解代码,再次认真学习了一下 Ruby 的闭包,特别是 block,proc 和 lambda 几种用法的异同,这次的周记就和大家分享一下心得. 闭包是 Ruby 相对其它语言特别优势之一,很多语言有闭包,但是唯有 Ruby 把闭包的优势发挥得淋漓尽致.Ruby 的哲学之一:同一个问题现实中有多种解决方法,所以 Ruby 中也有多种解法,所以闭包的使用也有多种方法.

  • Ruby中的block代码块学习教程

    1.什么是代码块 在Ruby中,{}或do...end之间的代码是一个代码块.代码块只能出现在一个方法的后边,它紧接在方法最后一个参数的同一行上,由yield关键字调用.例如: [1,2,3,4,5].each { |i| puts i } [1,2,3,4,5].each do |i| puts i end 块变量:以yield关键字调用block也可以传递参数,block中竖线(|)之间给出的参数名用于接收来自yield的参数. 竖线之间(如上例中的 | i |)的变量被称作块变量,作用和一

  • Ruby中使用Block、Proc、lambda实现闭包

    闭包(Closure),是指未绑定到任何对象的自由代码,闭包中的代码与任何对象和全局变量无关,只与执行此段代码的上下文相关. 今天我们简要的看一下ruby中的闭包实现. Ruby中的闭包实现有:Block,Proc,Lambada. 首先,我们来看Block. 复制代码 代码如下: ary = [1,2,3,4] ary.collect! do |a| a*a end ary.each do |a| puts a end 这段代码,我们使用了Array对象的block方法,将ary中的每个元素平

  • 深入理解Ruby中的block概念

    Ruby 里的 block一般翻译成代码块,block 刚开始看上去有点奇怪,因为很多语言里面没有这样的东西.事实上它还不错. First-class function and Higher-order function First-class function 和 Higher-order function 是函数式编程语言里面的概念,听起来好像很高端的样子,其实很很简单的. First-class functions 是指在某些语言里,函数是一等公民,可以把函数当做参数传递, 可以返回一个函

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

    block是什么? 在Ruby中,block并不罕见.官方对block的定义是"一段被包裹着的代码".当然,我觉得这样的解释不会让你变的更明白. 对block的一种更简单的描述是"一个block就是一段存储在一个变量中的代码,它和其他的对象一样,可以被随时的运行" 然后,咱们通过看一些代码,之后再把这些代码重构成Ruby中的block形式.通过代码来实际的感受,更加直观. 比如,对两个数做加法? puts 5 + 6 # => 11 嗯,这样写是可以的.但是,

  • 详解Ruby中的方法概念

    Ruby方法跟其他编程语言中的函数非常相似, Ruby方法用于捆绑到一个单元中的一个或多个重复的语句. 方法名称应以小写字母开始.如果一个方法的名称以大写字母开始,Ruby可能会认为这是一个常数,因此可以正确解析调用. 方法应该定义Ruby的之前调用他们,否则会引发一个异常未定义的方法调用. 语法: def method_name [( [arg [= default]]...[, * arg [, &expr ]])] expr.. end 所以,可以定义一个简单的方法如下: def meth

  • 详解Ruby中范围的概念

    范围无处不在:1月至12月,0至9日,50至67行,依此类推. Ruby支持范围,并允许我们使用多种方式的范围: 作为序列范围 作为条件范围 作为区间范围 作为序列范围: 首先,也许是最自然的使用范围来表达序列.序列有一个起点,一个终点和序列中的连续值的方法来生产. Ruby创建'' ..''和'' ...''范围内运算符使用这些序列.这两个点的形式建立一个包容性的范围,而三个点的形式创建了一个范围,不包括指定的高值. (1..5) #==> 1, 2, 3, 4, 5 (1...5) #==>

  • 进一步理解Java中的多态概念

    多态性有两种: 1)编译时多态性 对于多个同名方法,如果在编译时能够确定执行同名方法中的哪一个,则称为编译时多态性. 2)运行时多态性 如果在编译时不能确定,只能在运行时才能确定执行多个同名方法中的哪一个,则称为运行时多态性. 方法覆盖表现出两种多态性,当对象获得本类实例时,为编译时多态性,否则为运行时多态性,例如: XXXX x1 = new XXXX(参数列表); //对象获得本类实例,对象与其引用的实例类型一致 XXX xx1 = new XXX(参数列表); x1.toString();

  • 透彻理解Java中Synchronized(对象锁)和Static Synchronized(类锁)的区别

    本文讲述了Java中Synchronized(对象锁)和Static Synchronized(类锁)的区别.分享给大家供大家参考,具体如下: Synchronized和Static Synchronized区别 通过分析这两个用法的分析,我们可以理解java中锁的概念.一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全局锁(该锁针对的是类,无论实例多少个对象,那么线程都共享该锁).实例锁对应的就是synchronized关键字,而类锁(全局锁)对应的就是

随机推荐