[轉(zhuǎn)]深入理解ruby閉包

I recommend executing this file, then reading it alongside its output. Alteratively, you can give yourself a sort of Ruby test by deleting all the comments, then trying to guess the output of the code! A closure is a block of code which meets three criteria:
* 它可以作為一個值被傳入
* 在任何時候根據(jù)不同人的需要來執(zhí)行它
* 它可以使用創(chuàng)建它的那個上下文中的變量(即它是對封閉變量的訪問曲管,數(shù)學(xué)意義上的詞“封閉”)

def example(num)  
    puts  
    puts "------ Example #{num} ------"  
end  

-------------------- Section 1: Blocks ----------------------------
----------------------- 章節(jié)一:塊 -------------------------------

Blocks are like closures, because they can refer to variables from their defining context:
塊就像閉包唯灵,因為他們可以使用定義它們的那個上下文中的變量。

example 1

def thrice  
   yield  
   yield  
   yield  
end  
x = 5  
puts "value of x before: #{x}"  
thrice { x += 1 }  
puts "value of x after: #{x}"  

A block refers to variables in the context it was defined, not the context in which it is called:
一個塊使用定義它的上下文中的變量攻柠,而不是被調(diào)用的上下文中的變量米酬。

example 2

def thrice_with_local_x  
   x = 100  
   yield  
   yield  
   yield  
   puts "value of x at end of thrice_with_local_x: #{x}"  
end  
 
x = 5  
thrice_with_local_x { x += 1 }  
puts "value of outer x after: #{x}"  

A block only refers to existing variables in the outer context; if they don't exist in the outer, a
block won't create them there:
一個塊僅僅使用定義它的上下文中已經(jīng)存在的變量俺叭,如果變量不存在外于部上下文发魄,塊不會去創(chuàng)建他們。

example 3

thrice do # note that {...} and do...end are completely equivalent 注意{...}和do...end是完全相等的  
   y = 10  
   puts "Is y defined inside the block where it is first set?"  
   puts "Yes." if defined? y  
end  
puts "Is y defined in the outer context after being set in the block?"  
puts "No!" unless defined? y  

OK, so blocks seem to be like closures: they are closed with respect to variables defined in the context
where they were created, regardless of the context in which they're called.
But they're not quite closures as we've been using them, because we have no way to pass them around:
"yield" can only refer to the block passed to the method it's in.

We can pass a block on down the chain, however, using &:
所以塊看起來像閉包:他們封閉了定義它們的上下文中的變量钞瀑,不管他們在哪里調(diào)用沈撞。
但是在我們使用它們時,它們不完全是閉包雕什,因為我們沒有辦法傳遞他們缠俺。
“yield”僅僅可以將塊傳給它所在的方法。

example 4

def six_times(&block)  
    thrice(&block)  
    thrice(&block)  
end  
  
x = 4  
six_times { x += 10 }  
puts "value of x after: #{x}"  

So do we have closures? Not quite! We can't hold on to a &block and call it later at an arbitrary
time; it doesn't work. This, for example, will not compile:
def save_block_for_later(&block)
saved = █
end
But we can pass it around if we use drop the &, and use block.call(...) instead of yield:
所以我們是否有閉包贷岸?不完全壹士!我們不能保存一個&block,然后稍后在任意時間來調(diào)用它凰盔。
但是如果我們丟掉&墓卦,我們就有辦法傳遞它了,使用block,call(...)代替yield户敬。

example 5

def save_for_later(&b)  
    @saved = b  # Note: no ampersand! This turns a block into a closure of sorts. 注意:沒有&符號落剪!這個做法將一個塊變成了一個閉包。  
end    
save_for_later { puts "Hello!" }  
puts "Deferred execution of a block:"  
@saved.call  
@saved.call  

But wait! We can't pass multiple blocks to a function! As it turns out, there can be only zero
or one &block_params to a function, and the &param must be the last in the list.
None of these will compile:
def f(&block1, &block2) ...

def f(&block1, arg_after_block) ...
f { puts "block1" } { puts "block2" }
What the heck?
I claim this single-block limitation violates the "principle of least surprise." The reasons for
it have to do with ease of C implementation, not semantics.
So: are we screwed for ever doing anything robust and interesting with closures?
等等尿庐!我們不能傳遞多個塊給一個函數(shù)忠怖!一個函數(shù)只能接收0或1個&block_params參數(shù),并且&block_params必須是最后一個參數(shù)抄瑟。
我覺得這個單block違反了最小驚訝原則凡泣,因為這很容用C來實現(xiàn),不是語義上的。

------------------ Section 2: Closure-Like Ruby Constructs ----------------------------
-------------------- 章節(jié)二:閉包風(fēng)格的Ruby構(gòu)造器 ----------------------------------

實際上鞋拟,沒有骂维。當(dāng)我們傳遞一個塊&param,然后不帶&來使用param贺纲,這是Proc.new(&param)的隱式同義詞航闺。

example 6

def save_for_later(&b)  
    @saved = Proc.new(&b) # same as: @saved = b  
end  
  
save_for_later { puts "Hello again!" }  
puts "Deferred execution of a Proc works just the same with Proc.new:"  
@saved.call  

We can define a Proc on the spot, no need for the &param:
我們可以隨意定義一個Proc,不需要&param猴誊。

example 7

@saved_proc_new = Proc.new { puts "I'm declared on the spot with Proc.new." }  
puts "Deferred execution of a Proc works just the same with ad-hoc Proc.new:"  
@saved_proc_new.call  

Behold! A true closure!
But wait, there's more.... Ruby has a whole bunch of things that seem to behave like closures,
and can be called with .call:
看潦刃!一個真的閉包!
等等懈叹,還有跟精彩的乖杠。。澄成。Ruby還有一堆東西的行為看起來像閉包胧洒,并且能用.call來調(diào)用。

example 8

@saved_proc_new = Proc.new { puts "I'm declared with Proc.new." }  
@saved_proc = proc { puts "I'm declared with proc." }  
@saved_lambda = lambda { puts "I'm declared with lambda." }  
def some_method   
  puts "I'm declared as a method."  
end  
@method_as_closure = method(:some_method)  

puts "Here are four superficially identical forms of deferred execution:"  
@saved_proc_new.call  
@saved_proc.call  
@saved_lambda.call  
@method_as_closure.call  

So in fact, there are no less than seven -- count 'em, SEVEN -- different closure-like constructs in Ruby:
1. block (implicitly passed, called with yield)
2. block (&b => f(&b) => yield)
3. block (&b => b.call)
4. Proc.new
5. proc
6. lambda
7. method
Though they all look different, some of these are secretly identical, as we'll see shortly.
We already know that (1) and (2) are not really closures -- and they are, in fact, exactly the same thing.
Numbers 3-7 all seem to be identical. Are they just different syntaxes for identical semantics?

----------------- Section 3: Closures and Control Flow ----------------------------
-------------------- 章節(jié)三:閉包后控制流 --------------------------------------

No, they aren't! One of the distinguishing features has to do with what "return" does.
Consider first this example of several different closure-like things without a return statement.
They all behave identically:
實際上Ruby有不少于7中不同的閉包風(fēng)格的構(gòu)造器
盡管他們看起來不一樣环揽,有些其實是完全相同的略荡,我們后面馬上會看到。
我們已經(jīng)知道(1)和(2)不是真正的閉包歉胶,其實(1)和(2)是同樣的東西汛兜。
3-7看上去是完全一樣的,他們是不是語義相同的不用語法通今?

example 9

def f(closure)  
    puts  
    puts "About to call closure"  
    result = closure.call  
    puts "Closure returned: #{result}"  
    "Value from f"  
end  
  
puts "f returned: " + f(Proc.new { "Value from Proc.new" })  
puts "f returned: " + f(proc { "Value from proc" })  
puts "f returned: " + f(lambda { "Value from lambda" })  
def another_method  
    "Value from method"  
end  
puts "f returned: " + f(method(:another_method))  
  

But put in a "return," and all hell breaks loose!
但是加上一個“return”語句粥谬,一切都變的不可收拾。

example 10

begin  
    f(Proc.new { return "Value from Proc.new" })  
rescue Exception => e  
    puts "Failed with #{e.class}: #{e}"  
end  

The call fails because that "return" needs to be inside a function, and a Proc isn't really quite a full-fledged function: 這樣的調(diào)用會拋出異常因為“return”必須在一個函數(shù)內(nèi)辫塌,而一個Proc不是一個真正的完全獨立的函數(shù)漏策。

example 11

def g  
   result = f(Proc.new { return "Value from Proc.new" })  
   puts "f returned: " + result #never executed  
   "Value from g"               #never executed  
end  
 
puts "g returned: #{g}"  

Note that the return inside the "Proc.new" didn't just return from the Proc -- it returned
all the way out of g, bypassing not only the rest of g but the rest of f as well! It worked
almost like an exception.
This means that it's not possible to call a Proc containing a "return" when the creating
context no longer exists:
注意在“Proc.new”中的return語句不僅僅從Proc中返回,它將從g方法中返回臼氨,不僅繞過g方法中剩余的代碼
而且f方法中也是一樣的!它就像一個異常一樣掺喻。
這意味著當(dāng)創(chuàng)建Proc的上下文不存在時,無法調(diào)用一個包含“return”的Proc
本人的理解:
在Proc.new中的return語句储矩,會嘗試返回到創(chuàng)建它的上下文之后感耙,繼續(xù)執(zhí)行,如果創(chuàng)建它的上下文已經(jīng)“消失”或者返回到了全局上下文持隧,就會出現(xiàn)LocalJumpError即硼。
在#example10中,在全局上下文中用Proc.new創(chuàng)建了一個proc屡拨,在f方法中使用call只酥,然后proc會返回到創(chuàng)建它的上下文褥实,即全局上下文,于是出了LocalJumpError裂允。
在#example11中损离,在g方法的上下文中用Proc.new創(chuàng)建了一個proc,在f方法中使用call叫胖,然后proc會返回到創(chuàng)建它的上下文草冈,即g方法之后,這種情況不會出異常瓮增。

example 12

def make_proc_new  
  begin  
      Proc.new { return "Value from Proc.new" } 
 # this "return" will return from make_proc_new 這個“return”會返回到make_proc_new方法之外  
  ensure  
      puts "make_proc_new exited"  
  end  
end  

begin  
  puts make_proc_new.call  
rescue Exception => e  
  puts "Failed with #{e.class}: #{e}"  
end  

(Note that this makes it unsafe to pass Procs across threads.)
(注意跨線程的傳遞Proc會導(dǎo)致它不安全。)

A Proc.new, then, is not quite truly closed: it depends in the creating context still existing,
because the "return" is tied to that context.
Proc.new哩俭,不是一個真正完全的閉包绷跑。它依賴于創(chuàng)建它的上下文要一直存在,因為“return”和那個上下文聯(lián)席在了一起凡资。
本人的理解:
就和剛剛講的一樣砸捏,如果創(chuàng)建它的上下文已經(jīng)“消失”,在這里make_proc_new方法把創(chuàng)建的proc返回了出去隙赁,所以make_proc_new的上下文就不存在了垦藏,
這時候再使用call,也會發(fā)生LocalJumpError伞访。
Not so for lambda:
lambda就不是這個樣子:

example 13

def g  
    result = f(lambda { return "Value from lambda" })  
    puts "f returned: " + result  
    "Value from g"  
end  
  
puts "g returned: #{g}"  

And yes, you can call a lambda even when the creating context is gone:
你可以調(diào)用lambda掂骏,即使創(chuàng)建它的上下文已經(jīng)不在。

example 14

def make_lambda  
    begin  
        lambda { return "Value from lambda" }  
    ensure  
        puts "make_lambda exited"  
    end  
end  
  
puts make_lambda.call  

Inside a lambda, a return statement only returns from the lambda, and flow continues normally.
So a lambda is like a function unto itself, whereas a Proc remains dependent on the control
flow of its caller.
在lambda內(nèi)部厚掷,return語句僅僅從lambda中返回弟灼,代碼流程不會改變。
所以lambda就像一個方法一樣冒黑,而Proc需要依賴于它調(diào)用者的控制流田绑。
A lambda, therefore, is Ruby's true closure.
所以lambda才是ruby真正的閉包。
As it turns out, "proc" is a synonym for either "Proc.new" or "lambda."
Anybody want to guess which one? (Hint: "Proc" in lowercase is "proc.")
事實證明抡爹,"proc"是“Proc.new”或者“l(fā)ambda”的同義詞掩驱。
有人想猜一猜到底是哪個嗎?(提示:“Proc”的小寫是“proc”冬竟。)

example 15

def g  
    result = f(proc { return "Value from proc" })  
    puts "f returned: " + result  
    "Value from g"  
end  
  
puts "g returned: #{g}"  

The answer: Ruby changed its mind. If you're using Ruby 1.8, it's a synonym for "lambda."
That's surprising (and also ridiculous); somebody figured this out, so in 1.9, it's a synonym for
Proc.new. Go figure.
答案是如果你使用ruby1.8欧穴,那么它是“l(fā)ambda”的同義詞。
這真得讓人驚訝(而且荒謬)诱咏,有些人指出了這一點苔可,所以在ruby1.9,它是“Proc.new”的同義詞了袋狞。
I'll spare you the rest of the experiments, and give you the behavior of all 7 cases:
我就不進行剩下的實驗了焚辅,但是會給出所有的7種示例映屋。

# "return" returns from caller:  
#      1. block (called with yield)  
#      2. block (&b  =>  f(&b)  =>  yield)    
#      3. block (&b  =>  b.call)      
#      4. Proc.new  
#      5. proc in 1.9  
#  
# "return" only returns from closure:  
#      5. proc in 1.8  
#      6. lambda      
#      7. method  

-------------------- Section 4: Closures and Arity ----------------------------
--------------------- 章節(jié)四:閉包和元數(shù) ----------------------------------

注:arity => 一個方法或者函數(shù)可以接受的參數(shù)個數(shù)

The other major distinguishing of different kinds of Ruby closures is how they handle mismatched
arity-- in other words, the wrong number of arguments.
另外一個主要辨別不同種類的ruby閉包是他們?nèi)绾翁幚聿黄ヅ涞脑獢?shù),換句話說同蜻,就是不正確的參數(shù)個數(shù)棚点。
In addition to "call," every closure has an "arity" method which returns the number of expected
arguments:
除了“call”以外,每個閉包還有一個“arity”方法可以返回期望的參數(shù)個數(shù)湾蔓。

example 16

puts "One-arg lambda:"  
puts (lambda {|x|}.arity)  
puts "Three-arg lambda:"  
puts (lambda {|x,y,z|}.arity)  
 
puts "No-args lambda: "  
puts (lambda {}.arity) 
# This behavior is also subject to change in 1.9. #這個行為在1.9中也會有變化瘫析。 1.8中是-1,1.9中是0  
puts "Varargs lambda: "  
puts (lambda {|*args|}.arity)  

Watch what happens when we call these with the wrong number of arguments:
看看當(dāng)我們使用不真確的參數(shù)個數(shù)來調(diào)用他們時默责,會發(fā)生什么贬循。

example 17

def call_with_too_many_args(closure)  
   begin  
       puts "closure arity: #{closure.arity}"  
       closure.call(1,2,3,4,5,6)  
       puts "Too many args worked"  
   rescue Exception => e  
       puts "Too many args threw exception #{e.class}: #{e}"  
   end  
end  
 
def two_arg_method(x,y)  
end  
 
puts; puts "Proc.new:"; call_with_too_many_args(Proc.new {|x,y|})  
puts; puts "proc:"    ; call_with_too_many_args(proc {|x,y|})  
puts; puts "lambda:"  ; call_with_too_many_args(lambda {|x,y|})  
puts; puts "Method:"  ; call_with_too_many_args(method(:two_arg_method))  
 
def call_with_too_few_args(closure)  
   begin  
       puts "closure arity: #{closure.arity}"  
       closure.call()  
       puts "Too few args worked"  
   rescue Exception => e  
       puts "Too few args threw exception #{e.class}: #{e}"  
   end  
end  
 
puts; puts "Proc.new:"; call_with_too_few_args(Proc.new {|x,y|})  
puts; puts "proc:"    ; call_with_too_few_args(proc {|x,y|})  
puts; puts "lambda:"  ; call_with_too_few_args(lambda {|x,y|})  
puts; puts "Method:"  ; call_with_too_few_args(method(:two_arg_method))  

example 18

def one_arg_method(x)  
end  
  
puts; puts "Proc.new:"; call_with_too_many_args(Proc.new {|x|})  
puts; puts "proc:"    ; call_with_too_many_args(proc {|x|})  
puts; puts "lambda:"  ; call_with_too_many_args(lambda {|x|})  
puts; puts "Method:"  ; call_with_too_many_args(method(:one_arg_method))  
puts; puts "Proc.new:"; call_with_too_few_args(Proc.new {|x|})  
puts; puts "proc:"    ; call_with_too_few_args(proc {|x|})  
puts; puts "lambda:"  ; call_with_too_few_args(lambda {|x|})  
puts; puts "Method:"  ; call_with_too_few_args(method(:one_arg_method))  

Yet when there are no args...
當(dāng)他們沒有參數(shù)時。桃序。杖虾。

example 19

def no_arg_method  
end  
  
puts; puts "Proc.new:"; call_with_too_many_args(Proc.new {||})  
puts; puts "proc:"    ; call_with_too_many_args(proc {||})  
puts; puts "lambda:"  ; call_with_too_many_args(lambda {||})  
puts; puts "Method:"  ; call_with_too_many_args(method(:no_arg_method))  

注:#example17-19的結(jié)果在ruby1.8和1.9下,1.8下proc和lambda的執(zhí)行結(jié)果完全一樣媒熊,1.9下proc和Proc.new的結(jié)果完全相同奇适。

------------------ Section 5: Rant ----------------------------
------------------- 章節(jié)五:咆哮 -----------------------------

This is quite a dizzing array of syntactic options, with subtle semantics differences that are not
at all obvious, and riddled with minor special cases. It's like a big bear trap from programmers who
expect the language to just work.
真是讓人蛋疼的語法,這些語法上的微妙差異不是那么顯而易見的芦鳍,而且還有不少小的特殊情況嚷往。
Why are things this way? Because Ruby is:
為什么會這樣呢?因為ruby是:
(1) designed by implementation, and
(2) defined by implementation.
通過實現(xiàn)來設(shè)計和定義 (看不明白
The language grows because the Ruby team tacks on cool ideas, without maintaining a real spec apart from CRuby. A spec would make clear the logical structure of the language, and thus help highlight inconsistencies like the ones we've just seen. Instead, these inconsinstencies creep into the language, confuse the crap out of poor souls like me who are trying to learn it, and then get submitted as bug reports. Something as fundamental as the semantics of proc should not get so screwed up that they have to backtrack between releases, for heaven's sake! Yes, I know, language design is hard - but something like this proc/lambda issue or the arity problem wasn't so hard to get right the first time.
Yammer yammer.

--------------------- Section 6: Summary ----------------------------
--------------------- 章節(jié)六:總結(jié) -------------------------------

So, what's the final verdict on those 7 closure-like entities?
所以柠衅,那7個閉包風(fēng)格的實體最終結(jié)論如下:
"return" returns from closure
True closure? or declaring context...? Arity check?

  1. block (called with yield) N declaring no
  2. block (&b => f(&b) => yield) N declaring no
  3. block (&b => b.call) Y except return declaring warn on too few
  4. Proc.new Y except return declaring warn on too few
  5. proc <<< alias for lambda in 1.8, Proc.new in 1.9 >>>
  6. lambda Y closure yes, except arity 1
  7. method Y closure yes
    The things within each of these groups are all semantically identical -- that is, they're different
    syntaxes for the same thing:
    下面的分組中的每個在語義上都是相同的皮仁,也就是說,只是語法不同的同一個東西茄茁。
    1. block (called with yield)
    2. block (&b => f(&b) => yield)
    3. block (&b => b.call)
    4. Proc.new
    5. proc in 1.9
    6. proc in 1.8
    7. lambda
    8. method (may be identical to lambda with changes to arity checking in 1.9) 在1.9中參數(shù)檢查和lambda一樣
      Or at least, this is how I think it is, based on experiment. There's no authoritative answer other
      than testing the CRuby implementation, because there's no real spec -- so there may be other differences
      I haven't discovered.
      至少魂贬,根據(jù)實驗,我是這么認為的裙顽。除了測試CRuby實現(xiàn)外付燥,也沒有其他的官方答案。因為沒有真實的規(guī)范--所以也許和我發(fā)現(xiàn)的有些不同愈犹。
      The final verdict: Ruby has four types of closures and near-closures, expressible in seven syntactic
      variants. Not pretty. But you sure sure do cool stuff with them! That's up next....
      最終結(jié)論:Ruby有四個類型的閉包和近似閉包键科,使用7中語法變形來體現(xiàn),不是很好漩怎,但是你能他們來做一些很cool的東西勋颖。
      This concludes the "Ruby sucks" portion of our broadcast; from here on, it will be the "Ruby is
      awesome" portion.

------------------ Section 7: Doing Something Cool with Closures -----------------
------------------ 章節(jié)七:用閉包來做些Cool的東西 -------------------------------

Let's make a data structure containing all of the Fibonacci numbers. Yes, I said all of them.
How is this possible? We'll use closures to do lazy evaluation, so that the computer only calculates
as much of the list as we ask for.
我們創(chuàng)建一個數(shù)據(jù)結(jié)構(gòu)包含所有的菲波那契數(shù)。是的勋锤,我說“所有的”饭玲。
這怎么可能?我們將使用閉包來做到延遲求值叁执,所以計算機僅僅會計算我們所要的茄厘。
To make this work, we're going to use Lisp-style lists: a list is a recursive data structure with
two parts: "car," the next element of the list, and "cdr," the remainder of the list.
要讓他工作矮冬,我們要使用Lisp風(fēng)格的lists:一個包含2部分的可遞歸的數(shù)據(jù)結(jié)構(gòu):“car”,list中的下一個元素次哈,“cdr”胎署,list中剩余的元素。
For example, the list of the first three positive integers is [1,[2,[3]]]. Why? Because:
[1,[2,[3]]] <--- car=1, cdr=[2,[3]]
[2,[3]] <--- car=2, cdr=[3]
[3] <--- car=3, cdr=nil
Here's a class for traversing such lists:
這有一個類來遍歷這樣的list

example 20

class LispyEnumerable  
    include Enumerable  
  
    def initialize(tree)  
        @tree = tree  
    end  
  
    def each  
        while @tree  
            car,cdr = @tree  
            yield car  
            @tree = cdr  
        end  
    end  
end  
  
list = [1,[2,[3]]]  
LispyEnumerable.new(list).each do |x|  
    puts x  
end  

So how to make an infinite list? Instead of making each node in the list a fully built
data structure, we'll make it a closure -- and then we won't call that closure
until we actually need the value. This applies recursively: the top of the tree is a closure,
and its cdr is a closure, and the cdr's cdr is a closure....
所以如和去創(chuàng)建一個無限的list窑滞?我們通過創(chuàng)建一個閉包來代替原來在list中內(nèi)建的基本數(shù)據(jù)結(jié)構(gòu)琼牧。
除非我們真得須要數(shù)據(jù),否則我們不會調(diào)用它哀卫。它會是一個遞歸巨坊。。此改。

example 21

class LazyLispyEnumerable  
   include Enumerable  
 
   def initialize(tree)  
       @tree = tree  
   end  
 
   def each  
       while @tree  
           car,cdr = @tree.call # <--- @tree is a closure  
           yield car  
           @tree = cdr  
       end  
   end  
end  
 
list = lambda{[1, lambda {[2, lambda {[3]}]}]}
# same as above, except we wrap each level in a lambda 和前面的一樣抱究,只是多包了一個lambda  
LazyLispyEnumerable.new(list).each do |x|  
   puts x  
end  

example 22

Let's see when each of those blocks gets called:
讓我們來看看這些block是什么時候被調(diào)用的

list = lambda do  
    puts "first lambda called"  
    [1, lambda do  
        puts "second lambda called"  
        [2, lambda do  
            puts "third lambda called"  
            [3]  
        end]  
    end]  
end  
  
puts "List created; about to iterate:"  
LazyLispyEnumerable.new(list).each do |x|  
    puts x  
end  

Now, because the lambda defers evaluation, we can make an infinite list:
現(xiàn)在,因為lamdba的延遲求值带斑,我們可以創(chuàng)建無限list。

example 23

def fibo(a,b)  
    lambda { [a, fibo(b,a+b)] }
 # <---- this would go into infinite recursion if it weren't in a lambda 這個會進入死循環(huán)如果它不在lambda內(nèi)部  
end  
  
LazyLispyEnumerable.new(fibo(1,1)).each do |x|  
    puts x  
    break if x > 100 
   # we don't actually want to print all of the Fibonaccis! 我們實際上不會要打印所有的菲波那契數(shù)勋拟。  
end  

This kind of deferred execution is called "lazy evaluation" -- as opposed to the "eager
evaluation" we're used to, where we evaluate an expression before passing its value on.
(Most languages, including Ruby, use eager evaluation, but there are languages (like Haskell)
which use lazy evaluation for everything, by default! Not always performant, but ever so very cool.)
這就是延遲求值勋磕,和我們通常用得立即求值相反,我們在傳送值之前就求值了表達式敢靡。
大部分語言挂滓,包括ruby,都是立即求值啸胧,但是有些其他語言赶站,比如Haskell,他默認就是對任何都延遲求值纺念。
這不是一直是高性能的贝椿,但是這非常cool。
This way of implementing lazy evaluation is terribly clunky! We had to write a separate
LazyLispyEnumerable that knows we're passing it a special lazy data structure. How unsatisfying! Wouldn't it be nice of the lazy evaluation were invisible to callers of the lazy object?
這種實現(xiàn)延遲求值的方式是非常笨拙的陷谱。我們不得不寫一個單獨的LazyLispyEnumerable烙博,讓他知道我們傳了一個
特別的延遲數(shù)據(jù)結(jié)構(gòu)給他。能不能讓他更好的處理延遲求值烟逊,讓他對于調(diào)用者隱藏延遲對象渣窜?
As it turns out, we can do this. We'll define a class called "Lazy," which takes a block, turns it
into a closure, and holds onto it without immediately calling it. The first time somebody calls a
method, we evaluate the closure and then forward the method call on to the closure's result. 事實證明,我們可以這么做宪躯。我們定一個叫“Lazy”的類乔宿,然后使用block,把它轉(zhuǎn)成閉包访雪,然后保存它详瑞。

class Lazy  
    def initialize(&generator)  
        @generator = generator  
    end  
  
    def method_missing(method, *args, &block)  
        evaluate.send(method, *args, &block)  
    end  
  
    def evaluate  
        @value = @generator.call unless @value  
        @value  
    end  
end  
  
def lazy(&b)  
    Lazy.new &b  
end  

This basically allows us to say:
你可以這樣來使用:
lazy {value}
...and get an object that looks exactly like value -- except that value won't be created until the
first method call that touches it. It creates a transparent lazy proxy object. Observe:
然后就能得到一個看起來像指定的value一樣的對象—— 除了value只有在一次調(diào)用method的以后才會創(chuàng)建掂林。
它創(chuàng)建了一個透明的延遲代理對象。

example 24

x = lazy do  
    puts "<<< Evaluating lazy value >>>"  
    "lazy value"  
end  
  
puts "x has now been assigned"  
puts "About to call one of x's methods:"  
puts "x.size: #{x.size}"          # <--- .size triggers lazy evaluation .size會觸發(fā)延遲求值  
puts "x.swapcase: #{x.swapcase}"  

So now, if we define fibo using lazy instead of lambda, it should magically work with our
original LispyEnumerable -- which has no idea it's dealing with a lazy value! Right?
現(xiàn)在蛤虐,我們使用lazy代替lambda來創(chuàng)建菲波那契函數(shù)党饮,它應(yīng)該可以神奇地和我們來原的LispyEnumerable工作。
LispyEnumerable并不知道如何處理一個lazy value驳庭!是不是刑顺?

example 25

def fibo(a,b)  
    lazy { [a, fibo(b,a+b)] }  
end  
  
LispyEnumerable.new(fibo(1,1)).each do |x|  
    puts x  
    break if x.instance_of?(Lazy) || x > 200  
end  

example 26

car,cdr = fibo(1,1)  
puts "car=#{car}  cdr=#{cdr}"  

Here's the problem. When we do this:
問題就在這里,當(dāng)我們這樣賦值時:
x,y = z
...Ruby calls z.respond_to?(to_a) to see if z is an array. If it is, it will do the multiple
assignment; if not, it will just assign x=z and set y=nil.
Ruby會調(diào)用z.respond_to?(to_a)來看看z是否能變成一個數(shù)組饲常。如果可以蹲堂,它會進行多次賦值,
否則贝淤,它只會賦值 x=z 和 y = nil柒竞。
We want our Lazy to forward the respond_to? call to our fibo list. But it doesn't forward it,
because we used the method_missing to do the proxying -- and every object implements respond_to?
by default, so the method isn't missing! The respond_to? doesn't get forwarded; instead, out Lazy
says "No, I don't respond to to_a; thanks for asking." The immediate solution is to forward
respond_to? manually:

我們想要的Lazy來轉(zhuǎn)發(fā)respond_to?,讓它調(diào)用到我們的菲波那契播聪。但是它沒有做到朽基,因為我們用method_missing
來進行代理 -- 而每個對象默認都有respond_to?方法,所以無法觸發(fā)到method_missing离陶!respond_to?方法沒有被轉(zhuǎn)發(fā)稼虎,
所以Lazy說:“我沒有to_a方法,謝謝你的調(diào)用招刨■”最快捷的辦法是手動轉(zhuǎn)發(fā)respond_to?方法。

class Lazy  
    def initialize(&generator)  
        @generator = generator  
    end  
  
    def method_missing(method, *args, &block)  
        evaluate.send(method, *args, &block)  
    end  
  
    def respond_to?(method)  
        evaluate.respond_to?(method)  
    end  
  
    def evaluate  
        @value = @generator.call unless @value  
        @value  
    end  
end  
  
# And *now* our original Lispy enum can work:  
# 現(xiàn)在我們原來的Lispy enum就能工作了沉眶。  
  
example 27  
  
LispyEnumerable.new(fibo(1,1)).each do |x|  
    puts x  
    break if x > 200  
end  

Of course, this only fixes the problem for respond_to?, and we have the same problem for every other
method of Object. There is a more robust solution -- frightening, but it works -- which is to undefine
all the methods of the Lazy when it's created, so that everything gets forwarded.
當(dāng)然打却,這只是修改了respond_to?的問題,Object的其他方法也有同樣的問題谎倔。這里有一個更健壯的辦法柳击,雖然有點可怕,
但是它能工作传藏。那就是在Lazy對象創(chuàng)建時取消定義Lazy所有的方法腻暮,那樣就都能被轉(zhuǎn)發(fā)了。
And guess what? There's already a slick little gem that will do it:
其實已經(jīng)有一個gem做了這樣的事:
http://moonbase.rydia.net/software/lazy.rb/
Read the source. It's fascinating.
看看它的源碼把毯侦,非常讓人著迷哭靖。

---------------------------- Section 8: Wrap-Up ----------------------------
------------------------------- 章節(jié)八:總結(jié) -------------------------------

假設(shè)你有一個對象須要一個網(wǎng)絡(luò)或者數(shù)據(jù)庫的調(diào)用才能被創(chuàng)建,或者會一旦創(chuàng)建會占用大量內(nèi)存侈离。你也不知道什么 時候會用到它试幽。使用lazy將防止它消耗資源,除非它真得需要卦碾。Hibernate使用延遲加載來阻止不必要的數(shù)據(jù)庫查詢铺坞, 而Hibernate做到它需要或多或少的Java對象起宽。(不像ActiveRecord,它依賴于一個base class來做到延遲加載)
Ruby可以使用更少的代碼做到相同的事情济榨。
That's just an example. Use your imagination.
這只是個示例坯沪,發(fā)揮你的想象力。
If you're a functional langauge geek, and enjoyed seeing Ruby play with these ideas from Lisp and
Haskell, you may enjoy this thread:
如果你是個函數(shù)式語言的極客擒滑,并且喜歡使用Ruby的這些來自于Lisp和Haskell的特性腐晾,你也許會想加入
http://redhanded.hobix.com/inspect/curryingWithArity.html

OK, I'll stop making your brain hurt now. Hope this has been a bit enlightening! The experience
of working it out certainly was for me.
好了,我不再傷害你的大腦了丐一。希望這能夠給你一點啟發(fā)藻糖!理解這些的經(jīng)驗對我非常有用。

Paul

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末库车,一起剝皮案震驚了整個濱河市巨柒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌柠衍,老刑警劉巖洋满,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異珍坊,居然都是意外死亡芦岂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門垫蛆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腺怯,你說我怎么就攤上這事袱饭。” “怎么了呛占?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵虑乖,是天一觀的道長。 經(jīng)常有香客問我晾虑,道長疹味,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任帜篇,我火速辦了婚禮糙捺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘笙隙。我一直安慰自己洪灯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布竟痰。 她就那樣靜靜地躺著签钩,像睡著了一般掏呼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铅檩,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天憎夷,我揣著相機與錄音,去河邊找鬼昧旨。 笑死拾给,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的臼予。 我是一名探鬼主播鸣戴,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼粘拾!你這毒婦竟也來了窄锅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤缰雇,失蹤者是張志新(化名)和其女友劉穎入偷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體械哟,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡疏之,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了暇咆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锋爪。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爸业,靈堂內(nèi)的尸體忽然破棺而出其骄,到底是詐尸還是另有隱情,我是刑警寧澤扯旷,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布拯爽,位于F島的核電站,受9級特大地震影響钧忽,放射性物質(zhì)發(fā)生泄漏毯炮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一耸黑、第九天 我趴在偏房一處隱蔽的房頂上張望桃煎。 院中可真熱鬧,春花似錦大刊、人聲如沸备禀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曲尸。三九已至赋续,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間另患,已是汗流浹背纽乱。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昆箕,地道東北人鸦列。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像鹏倘,于是被迫代替她去往敵國和親薯嗤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,666評論 2 350

推薦閱讀更多精彩內(nèi)容