最近更新: 2007-06-28

Block and Proc

Ruby 可以將程式碼參數化,Ruby 稱被參數化的程式碼為 block 。Ruby 語法以 {||} 表示一個 block ,其中的 || 為參數列宣告,若無參數則可省略。

Ruby 的 Proc 類似 ECMAScript 的 Function。在 ECMAScript 中使用關鍵字 function 即可配置一個 Function 實例。 Ruby 則使用 Kernel::procKernel::lambda 方法 (兩者相同) 或是直接建構一個 Proc 實例(Proc.new),需提供一個 block 作為引數。

Ruby: proc { |arguments| codes }
ECMAScript: function(arguments) { codes }
Block and Proc

Block 無法單獨存在,只能作為 Ruby 指令或呼叫方法時的引數。Ruby 會主動將 block 參數化,程序員僅需利用流程指令 yield 即可將流程轉移到被參數化的 block 中運行。

Block way. 這種用法較常見。

def nP(n)
    if block_given?
        yield n    # yield to black

    else
        puts 'no block'
    end
end

Proc way. 細節後述。

def nP(n, &p)
    if block_given?
        p.call n    # call proc p

    else
        puts 'no block'
    end
end

用例:

nP('hello') {|n| puts n}
nP(10) do |n|
    n.times do
        puts 'a'
    end
end

不論是用 block 或是 Proc ,都可以用 Kernel::block_given? 方法判斷使用方有無傳遞 block 。

Proc::call 方法也可以運算子 [ ] 形式調用。上例之 p.call n 亦可寫成 p[n]

def nP1(n)
    if block_given?
        yield n    # yield to black

    else
        puts 'no block'
    end
end

def nP2(n, &p)
    if block_given?
        p[n]    # call proc p

    else
        puts 'no block'
    end
end

ECMAScript 只有 Function 類,沒有 block 與 proc 的區分。上述的 Ruby 程式,以 ECMAScript 表達如下列所示:

function nP(n, p) {
    return p(n);
}

nP(10, function(n){print(n)});
nP(10,
  function(n) {
    for (var i = 0; i < n; ++i)
      print('a');
  }
);
Block and Method

定義方法時,若參數名稱前冠上 & 符號, Ruby 將自動轉換 block 為 Proc 實例(隱性調用 Proc.new),令其成為具名參數。

def nP(n, &p)
    p.call n
end

def mP(m, p)
    p.call m
end

nP('john') {|name| puts name}

mP('bob', Proc.new {|name| puts name} )
mP('bob', proc {|name| puts name} )  # Kernel::proc

大多數情形,我們只需要傳遞一段程式 (一個block) ,所以 Ruby 提供了自動轉換 block 為 Proc 實例的機制。作為待轉換為具名參數的程式碼區塊引數,必須位於方法定義之參數列的最後一個。

如果要傳遞多段程式,則不適用上述轉換機制。程序員必須明確指示處理動作。

def xP(n, &p1, &p2)  # ERROR!

end

def xP(n, p1, p2)
    p1.call n
    p2.call n
end

xP(5,
    proc {|n| puts n },
    proc {|n| n.times {puts 'a'} }
)

自動轉換 block 為 Proc 實例的機制通常應用於動態繫結的場合。

class DP
    def initialize(&p)
        @do = p
    end
    def do(n)
        @do.call n
    end
end

d = DP.new {|n| puts n}
d.do 5

Block 與 Closure 的應用可參考 Martin 《Closure》。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/3547373.html

樂多舊回應
未留名 (#comment-21912593)
Fri, 12 Aug 2011 12:12:42 +0800
Kernel::proc 、Kernel::lambda方法兩者不同,參見:http://en.wikipedia.org/wiki/Closure_(computer_science)
未留名 (#comment-21912637)
Fri, 12 Aug 2011 12:36:14 +0800
我寫下這篇文章的時間是 2007 年,Ruby 版本是 ruby 1.8。在那個版本,kernel::proc 與 kernel::lambda 是同一個東西。

為了確認我的記憶沒錯,我搜尋了一下,確定如此。例如:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/11924

Ruby 1.9 後, kernel::lambda 多了檢查參數的動作。這可突顯 lambda 的特性。

"lambda { |...| block } Equivalent to Proc.new, except the resulting Proc objects check the number of parameters passed when called." (http://www.ruby-doc.org/core/classes/Kernel.html#M001447)

因為必須傳參數,它才能把參數被傳遞當時的值,保存起來(將當時的狀態值封入閉鎖空間)。