Ruby中的block概念的理解
来源: 阅读:693 次 日期:2015-04-28 14:34:05
温馨提示: 小编为您整理了“Ruby中的block概念的理解”,方便广大网友查阅!

Ruby中的block概念的理解:

文中给出了Javascript代码块与Ruby代码块的对比,需要的朋友可以参考下

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 创建的对象的行为是一样的,比如

5

def hello

(1..10).each { |e| return e}

return "hello"

end

hello # => 1

这感觉真是有点悲催。

结语

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

更多信息请查看IT技术专栏

更多信息请查看网络编程
手机网站地址:Ruby中的block概念的理解
由于各方面情况的不断调整与变化, 提供的所有考试信息和咨询回复仅供参考,敬请考生以权威部门公布的正式信息和咨询为准!

2025国考·省考课程试听报名

  • 报班类型
  • 姓名
  • 手机号
  • 验证码
关于我们 | 联系我们 | 人才招聘 | 网站声明 | 网站帮助 | 非正式的简要咨询 | 简要咨询须知 | 加入群交流 | 手机站点 | 投诉建议
工业和信息化部备案号:滇ICP备2023014141号-1 云南省教育厅备案号:云教ICP备0901021 滇公网安备53010202001879号 人力资源服务许可证:(云)人服证字(2023)第0102001523号
云南网警备案专用图标
联系电话:0871-65317125(9:00—18:00) 获取招聘考试信息及咨询关注公众号:
咨询QQ:526150442(9:00—18:00)版权所有:
云南网警报警专用图标
Baidu
map