以前用 block 的时候非常爽 users.select { |user| user.id > 33 } users.sort_by { |user| user.post_count }
虽然不知道其背后的原理,但知道怎么用,并且对这些语法有一见如故相见恨晚的感觉。忍不住手痒痒看了 ruby 元编程,才知道原来这叫 block,并且知道了它本质上也是个参数,并且知道了它在函数内部的实现原理,给我带来了两个坏处: 1、原来这么漂亮的语法只是个障眼法,自从知道它本质上是个参数以后就觉得不好玩了 2、实现的原理有些绕,每次使用 block 的时候忍不住脑补它在函数内部是怎么 yield 的,累!
有一个故事不知道真假,一个男妇科医生,每天面对女人的下体,对女人的下体甚至比女人自己还了解,后来他阳痿了。出于职业习惯,每次他 XX 的时候忍不住去思考女人的生殖系统,构造、神经、内分泌、高潮原理,思考到头疼
了解实现原理是基础吧。一行代码后面发生了什么不清楚怎么能够放心。靠谱的程序员应该看到语法就应该猜测到有什么方式实现,然后去差查一下验证自己的猜测对不对,然后放掉就可以了。
话说回来“对 ruby 了解的太深入并不是好事”另外一个意思:重心不应该放在 ruby 语言本身上面,而是用 ruby 来做自己想要的东西,这个才是难的地方。
《七周七語言》裏訪談了 Matz,Matz 被問到最喜歡 Ruby 什麼的時候,就說最喜歡 block。既然這裏是瞎扯淡,我就把當時的讀書筆記抄一下:
Matz: 我喜歡它寓編程於樂的方式。說到某個具體的技術點,我最喜歡的是“代碼塊”(block)。代碼塊即是一種易於控制的高階函數,也爲 DSL 及其他特性的實現提供了極大的靈活性。(p. 9) 這裏 Matz 說謊了。block 很像高階函數,但不是函數。
->(x){x+3}.call 3
返回 6. 而
{|x| x + 3}.call 3
將導致
syntax error
. Ruby 的祖先 Smalltalk 中,block
倒是可以接受消息的:[:x | x + 3] value: 3
返回 6. 所以 Ruby 的 block 是個奇怪的東西。縱向來說,它很像 Smalltalk 的 block,橫向來說,它很像匿名函數。但是事實上,它和兩者都不一樣。Ruby 的口號是「Principle of Least Surprise」,但是這個 block 卻讓我吃驚。 語意上塊讓人迷惑,語法上也不好。塊有兩種表達法:大括號或者 do...end,問題在於優先級是不同的。例如
f x {|x| puts x}
和f x do |x| puts x end
是不一樣的,前者等於f(x {...})
,後者等於f(x) do...end
。初學的時候很容易搞混。 Matz 在他寫的《まつもとゆきひろコードの世界 : スーパー・プログラマになる 14 の思考法》一書中提到了設計塊的緣由:
- 減少對象的生成數,因爲早期 Ruby 生成閉包對象的代價很高。
- 外觀上看起來像控制結構。 Matz 還提到,傾向於使用高階函數的 OCaml 的 2239 個庫函數,沒用函數參數的佔 87.2%,用一個函數參數的佔 12.1%,也就是有兩個以上的不到 1%。因此,大多數情況下,只能有一個參數的塊也夠用了。 所以說,塊就是一個語法糖,讓習慣過程式編程的程序員可以使用類似高階函數的東西(同時讓 Smalltalk 和 Lisp 的來客大吃一驚)。
原来这么漂亮的语法只是个障眼法,自从知道它本质上是个参数以后就觉得不好玩了
这个逻辑无法理解。知道本质后,应该惊叹于 Ruby 的 一致性
, 统一
才对。其实 Ruby 的很多魔法就是没有魔法。
实现的原理有些绕,每次使用 block 的时候忍不住脑补它在函数内部是怎么 yield 的,累!
那是因为你还没有洗脑成功,等你洗脑成功了,就不会去想了。
至于楼主最后给的例子,不具有类比性,因为 Ruby 本来就很 简单
, 可能你从 Python 的角度,来看 Ruby 有点过于 魔幻化
了。那个医生错了,错在他不该了解那么多... 而你对了,因此这才算开始正确的认识 Ruby 了。
@zw963 這一句是 Matz 書裏的,我自己的從下一段纔開始。只能有一個參數,結合上下文應該不會誤解吧。例如樓主的例子, { |user| user.id > 33 }
,塊只能收user
一個參數,不能另外傳參數給它。
例如樓主的例子, { |user| user.id > 33 },塊只能收 user 一個參數,不能另外傳參數給它。
好吧,如果你这个 例如
是在解释下面的话,
Matz還提到,傾向於使用高階函數的OCaml的2239個庫函數,沒用函數參數的佔87.2%,用一個函數參數的佔12.1%,也就是有兩個以上的不到1%。因此,大多數情況下,只能有一個參數的塊也夠用了。
我可以很肯定,你完全错会松本的意思了。这个所谓的 参数
, 指的是:楼主所谓的那个 本质上是个参数
, 也就是说; { |user| user.id > 33 }
这是一个参数。而所谓的两个参数,如果用 Ruby 写,也许是这个样子:
meth {|x| x > 33 } {|y| y > 44 }
很明显,这是有点丑陋的。同样的需求,在 Ruby 下当然也可以实现,是这样做:
proc = proc {|x| x > 33 }
meth(proc) {|y| y > 44 }
而这样的需求,在 Rails 里面,还有很多 gem 里面,也是有的。只不过,这就是松本所说的 不到1%
.
users.select { |user| user.id > 33 }中,Block 设计的本质是代表了了什么?这里的 select 虽然只是一个普通的方法,但它的目的却与普通意义上的 OOP 里的方法不同(普通的 OOP 不会这么设计,因为 OOP 里方法调用通常代表了向对象发送消息,改变或查询对象的状态),但它的语义实际上类似于 for 循环这样的语言基本语义,可以被视为一种自定义的基本语义。从一定程度上跳出了普通方法的框框,向问题域迈进了一步。
为什么要这么使用 Block 而违反普通的 OOP 做法。它有两个好处: 1,第一个好处很明显,可读性大大增强。 2,第二个原因是:普通的 API 设计方法存在一种天然的陷阱,那就是不管怎样封装,大过程虽然比小过程抽象层次更高,但本质上还是过程,受到过程语义的制约。也就是说,通过基本元素/语义构造更高级抽象元素/语义的时候,语言的构造规则很大程度上限制了抽象的维度。
但 Block 的抽象维度既不同于过程式语言的过程抽象,也不同于 OOP 的数据抽象,而是《SICP》中所说的“元语言抽象”。不同于传统的 API 设计,它根据问题域选取适当的抽象维度,利用语言的基本语法构造领域特定的语义和语法。
这也是为什么 Ruby 的语法这么强大的本质原因之一。理解 Block 这么设计和使用的深层原因,理解元语言抽象对 Ruby 和 Rails 的重要意义,岂不对 Rails 编程本身会有更深的体会吗?