你必須很努力

Ruby 中的 Block、Proc、Lambda 是什麼?

2019/11/15
字數統計: 4.3k閱讀時間: 18 min

在 Ruby 中容易搞混(學不好),且面試經常會被問的問題:

「請說明 Block、Proc、Lambda 是什麼」
「Block 中的 do..end 與 花括號 { } 差異」
「請說明 Proc 與 Lambda 區別」
「Rails 的 scope 為什麼用 Lambda?」
「要怎麼把 Block 轉成 Proc、Lambda?」
「要怎麼把 Proc、Lambda 轉成 Block?」

上述常見問題,一次滿足!!

Block (程式碼區塊)

什麼是 Block ?

Ruby 是一款相當徹底「物件導向 OOP (Object-Oriented Programming)」的程式語言,絕大部分的東西都是物件,而 Block 是少數的例外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
list = [1, 3, 5, 7, 9, 10, 12]

list.map { |i| i * 2 }
# map 印出如下
2
6
10
14
18
20
24
=> [2, 6, 10, 14, 18, 20, 24] # 回傳值
# map 印出如上


list.select { |j| p j.even? }
# select 印出如下
false
false
false
false
false
true
true
=> [10, 12] # 回傳值
# select 印出如上


list.reduce { |x, y| p x + y }
# reduce 印出如下
4
9
16
25
35
47
=> 47 # 回傳值
# reduce 印出如上


list.each do |num|
p num * 2
end
# each 印出如下
2
6
10
14
18
20
24
=> [1, 3, 5, 7, 9, 10, 12] # 回傳值
# each 印出如上


# 從上得知,map、select、reduce 與 each 的回傳值(return value)不同
# map、select、reduce 會回傳一個新陣列
# each 回傳 receiver

在 Ruby 中,花括號 {}do..end 就是 Block,要接在方法(method)後面,且無法單獨存活,否則會出錯。

1
2
3
4
5
6
7
8
9
{ puts "無法單獨存活,會出錯" }  # SyntaxError


do
puts "無法單獨存活,會出錯" # SyntaxError
end


# Block 無法單獨存活,會噴錯誤訊息

Block 不是物件,不能單獨存在。

Block 只是附加在方法後面,等著被程式碼呼叫的一段程式碼。

Block 中 花括號 {}do..end 差異

一般來說,若可以一行寫完會使用 {},若遇上較複雜的判斷,需寫一行以上時,則會使用 do..end

除此之外,還有別的差異嗎?

請看以下範例:

1
2
3
4
5
list = [1, 2, 3, 4, 5]

p list.map{ |x| x * 2 } # [2, 4, 6, 8, 10]

p list.map do |x| x * 2 end # <Enumerator: [1, 2, 3, 4, 5]:map>

原來 花括號 {}do..end 還有 優先順序 的不同

花括號 {} 優先順序大於 do..end

上述範例原形為

1
2
3
4
5
6
list = [1, 2, 3, 4, 5]

p(list.map{ |x| x * 2 } # [2, 4, 6, 8, 10]

p(list.map) do |x| x * 2 end # <Enumerator: [1, 2, 3, 4, 5]:map>
# 因為優先順序較低,所以變成先跟 p 結合了,造成後面附掛的 Block 就不會被處理了

如何執行 Block 的內容?

使用 yield 方法

如果想讓附掛的 Block 執行內容,可以使用 yield 方法,能暫時先把控制權交給 Block ,等 Block 執行結束後再把控制權交回來。

1
2
3
4
5
6
7
8
9
10
11
12
13
def hi_block
puts "開始"
yield # 把控制權暫時讓給 Block
puts "結束"
end

hi_block { puts "這裡是 Block" }
# 印出結果如下
開始
這裡是 Block
結束
=> nil
# 印出結果如上

傳參數給 Block

會發現不管在 list.map { |i| i * 2 }list.each do |num| p num * 2 end 的 Block 中,那個 |i||num| 是什麼?
Block 中包住 inum 的 | 唸做 pipe,中間的 inum 是匿名函數的參數,稱作 token,其實是 Block 範圍裡的區域變數,離開 Block 之後就會失效了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
list = [1, 3, 5, 7, 9, 10, 12]

list.map { |i| i * 2 }
# 變數 i 只有在 Block 裡有效,會產生 [2, 6, 10, 14, 18, 20, 24]

list.each do |num|
p num * 2
end
# 變數 num 只有在 Block 裡有效,會依序印出 2、6、10、14、18、20、24


puts i # 離開 Block 之後就失效,出現找不到變數的錯誤 (NameError)
puts num # 離開 Block 之後就失效,出現找不到變數的錯誤 (NameError)

# 變數名稱自定義,會取有意義的名稱,讓人看到能知道是什麼,而不是取無意義的 i

所以, inum 是怎麼來的?
事實上,它就只是你在使用 yield 方法把控制權轉讓給 Block 的時候,順便把值帶給 Block 而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def hi_block
puts "開始"
yield "媽,我在這" # 也可寫 yield("媽,我在這")
puts "結束"
end


hi_block { |x| puts "這裡是 Block,#{x}" }
# 印出結果如下
開始
這裡是 Block,媽,我在這
結束
=> nil
# 印出結果如上

yield 後面可以帶 1 個或以上的參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 範例1 (帶 1 個參數)
def hi_block
puts "開始"
yield 123 # 把控制權暫時讓給 Block,並且傳數字 123 給 Block
puts "結束"
end

hi_block { |x| # 這個 x 是來自 yield 方法
puts "這裡是 Block,我收到了 #{x}"
}
# 印出結果如下
開始
這裡是 Block,我收到了 123
結束
=> nil
# 印出結果如上


# 範例2 (帶 2 個參數)
def tow_parm
yield(123, "參數2")
end

tow_parm { |m, n|
puts %Q(我說數字 #{m},你回#{n}~)
}
# 印出結果如下
我說數字 123,你回參數2~
=> nil
# 印出結果如上

yield 進階使用

範例 1:
第 7 行的 iyield 出去找了第 11 行的 ix 是實體變數 @veach 方法印出陣列中的數字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Map 
def initialize
@v = [1, 2, 3, 4]
end

def each_print
@v.each { |i| puts yield i } if block_given?
end
end

i = "多看幾次就會理解了"
a_obj = Map.new
a_obj.each_print{ |x| "#{i} #{x}" }

# 印出結果如下
多看幾次就會理解了 1
多看幾次就會理解了 2
多看幾次就會理解了 3
多看幾次就會理解了 4
=> [1, 2, 3, 4]
# 印出結果如上

範例 2:
第 4 行的 yield 將 counter 帶去 method 外面找 list 後面的 Block,因 first 預設為 1 ,得知 yield 後面的 counter 預設也為 1,成為外面 Block 中 |ary| 的參數,待 Block 執行完後再回到第 4 行繼續往下執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def list(array, first = 1)
counter = first
array.each do |item|
puts "#{yield counter}. #{item}"
counter = counter.next
end
end


list(["a","b","c"]) { |ary| ary * 3 }
# 印出結果如下
3. a
6. b
9. c
=> ["a", "b", "c"]
# 印出結果如上

list(["a","b","c"], 100) { |ary| ary * 3 }
# 印出結果如下
300. a
303. b
306. c
=> ["a", "b", "c"]
# 印出結果如上

list(["Ruby", "Is", "Fun"], "A") { |ary| ary * 3}
# 印出結果如下
AAA. Ruby
BBB. Is
CCC. Fun
=> ["Ruby", "Is", "Fun"]
# 印出結果如上

Block 的回傳值

其實 yield 方法除了把控制權暫時的讓給後面的 Block 之外

Block 最後一行的執行結果也會自動變成 Block 的回傳值

所以可把 Block 當做判斷內容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 範例1
def dog
puts "汪!!汪!!汪!!"
end

dog { puts "你看不見我~~" } # 汪!!汪!!汪!!
# 如果沒有 yield,寫在 Block 裡面的東西,是不會有反應的


# 範例2
def say_hello(list)
result = []
list.each do |i|
result << i if yield(i) # 如果 yield 的回傳值是 true 的話...
end
result
end

p say_hello([*1..10]) { |x| x % 2 == 0 } # [2, 4, 6, 8, 10]
p say_hello([*1..10]) { |x| x < 5 } # [1, 2, 3, 4]
p say_hello([*1..10]) { |x| return x < 5 } # 會產生 LocalJumpError 的錯誤
p say_hello([*1..10]) # 會產生 LocalJumpError (no block given (yield)) 錯誤訊息

上述範例 say_hello 方法,會根據 Block 的設定條件,挑出符合條件的元素,需特別留意在 Block 裡加入 return 會造成 LocalJumpError 的錯誤,因為 Block 不是一個方法,它不知道你要 return 到哪裡去而造成錯誤。

Block 不是參數

Block 像寄生蟲一樣得依附或寄生在其他的方法或物件,但它不是參數,以下範例中, name 才是參數,但 Block 不是。

1
2
3
4
5
def say_hello(name)
p name
end

say_hello("小菜") { puts "這是 Block"} # "小菜"

上面這段程式碼執行後不會有任何錯誤,但 Block 裡面也不會被執行。

怎判斷有無 Block?

有一種狀況是方法裡有 yield,但是呼叫方法的時候卻沒有 Block 的話...

1
2
3
4
5
def say_hello
yield
end

say_hello # 會產生 LocalJumpError (no block given (yield)) 錯誤訊息

會出現 LocalJumpError (no block given (yield)) 的錯誤訊息。

在這種狀況,要讓方法呼叫的時候也能正常執行

可以使用 Ruby 提供的一個判斷方法 block_given?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 範例1
def hi_block
if block_given? # 判斷執行方法的後面有沒有跟 Block
yield
else
"no block"
end
# 上面 5 行可簡寫成 block_given? ? yield : "no block"
end

hi_block # "no block"
hi_block { "hello" } # "hello"
hi_block do "hello" end # "hello"


# 範例2
def hello_world
yield('小菜') if block_given? # 判斷執行方法的後面有沒有跟 Block
# 也可寫 yield '小菜' if block_given?
end

p hello_world # nil
hello_world {|x| puts "#{x}" } # 小菜


# 範例3
def say_hello(name)
yield name if block_given? # 判斷執行方法的後面有沒有跟 Block
end

say_hello(puts "hi") # hi

Block 特性

總結上述所說特性

  1. 不是物件、不是參數
  2. 不能單獨存在,只是附加在方法後面,等著被程式碼呼叫的一段程式碼。
  3. Block 最後一行的執行結果也會自動變成 Block 的回傳值
  4. Block 內不能使用 return
  5. 不能賦值給其他物件

雖然 Block 不是物件,不能單獨存在

但 Ruby 有內建兩個方法使 Block 物件化且單獨存在: Proc 和 Lamda

Proc

Proc 是程序物件,可以將 Ruby 的程式碼保存起來,並且在需要的時候再執行它,或當作 Block 傳入其他函數。

Proc.new 後面接一個 Block 就可以產生一個 Proc 的物件,物件化後就是一個參數,接著可以使用 call 方法來執行 Block 內的程式碼。

1
2
3
4
5
6
7
8
proc1 = Proc.new { puts "Block 被物件化囉" }   # 使用 Proc 類別可把 Block 物件化
# 也可以寫成
proc2 = Proc.new do
puts "Block 被物件化囉"
end

proc1.call # Block 被物件化囉
proc2.call # Block 被物件化囉

Proc 中不能加入 return

return 不要寫在 Proc 的 Block 裡,否則程式碼執行到這段後就會停止(return 完後立即結束執行),程式碼不會繼續往下走。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def hi_proc
p "strat"
hi_proc = Proc.new { return "執行完這段就停止了" }
hi_proc.call
p "end"
end

p hi_proc
# 顯示結果如下
"strat"
"執行完這段就停止了"
=> "執行完這段就停止了"
# 顯示結果如上

# "end"不會印出,因為執行完第 3 行就停止了

Proc 可帶參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 範例1
hi_river = Proc.new { |name| puts "你好,#{name}"}
# 也可寫成 hi_river = proc { |name| puts "你好,#{name}" }

hi_river.call("小菜在這裡") # 你好,小菜在這裡


# 範例2 (帶參數)
cal = Proc.new { |num| num * 5 }
# 也可寫成 cal = proc { |num| num * 5 }

cal.call(3) # 15


# 範例3 (帶參數)
def total_price(price)
Proc.new { |num| num * price }
# 也可寫成 proc { |num| num * price }
end

n1 = total_price(50)
n2 = total_price(30)

puts "n1 要 #{n1.call(2)} 元,而 n2 要 #{n2.call(5)} 元"
# n1 要 100 元,而 n2 要 150 元

Proc 呼叫方式

要執行一個 Proc 物件,除了 call 方法之外,還有以下幾種使用方法:

1
2
3
4
5
6
7
8
9
10
11
hi_river = Proc.new { |name| puts "你好,#{name}"}


hi_river.call("小菜在這裡") # 使用 call 方法
hi_river.("小菜在這裡") # 使用小括號(注意,方法後面有多一個小數點)
hi_river["小菜在這裡"] # 使用中括號
hi_river === "小菜在這裡" # 使用三個等號
hi_river.yield "小菜在這裡" # 使用 yield 方法

# 上述 5 種方法皆印出
# 你好,小菜在這裡

Lambda

Block 除了能轉成 Proc,Block 也可以轉成 Lambda,與 Proc 有些微不同:

  1. retrun
  2. 參數的判斷方式 (是否會檢查參數的數量正確性)

Proc、Lambda 怎麼分

1
2
3
4
5
6
7
8
9
10
p1 = Proc.new {|x| x + 1 }
p2 = proc {|x| x + 1 } # Proc 的另種寫法
l1 = lambda {|x| x + 1 }
l2 = ->(x) { x + 1 } # lambda 的另種寫法


puts "p1: #{p1.lambda?}, #{p1.class}" # p1: false, Proc
puts "p2: #{p2.lambda?}, #{p2.class}" # p2: false, Proc
puts "l1: #{l1.lambda?}, #{l1.class}" # l1: true, Proc
puts "l2: #{l2.lambda?}, #{l2.class}" # l2: true, Proc

現在我們知道

Proc 和 Lambda 一樣都是屬於 Proc 物件

上面 p1p2l1l2 都可以使用 call 方法來執行,其中我們可以用 lambda? 來判斷它是不是 Lambda,如果不是那它就是 Proc。

Lambda 裡可加入 return

Lambda 與 Proc 的其中一個差異是 return 值不一樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def hi_lambda
p "strat"
hi_lambda = lambda { return p "會繼續往下執行" }
hi_lambda.call
p "end"
end


p hi_lambda
# 顯示結果如下
"strat"
"會繼續往下執行"
"end"
"end"
=> "end"
# 顯示結果如上

一次比較 Proc 和 Lambda 的 return 值

1
2
3
4
5
6
7
8
9
10
def test_return(callable_object)
callable_object.call * 5
end

la = lambda { return 10 } # 也可寫成 la = ->{ return 10 }
pr = proc { return 10 } # 也可寫成 pr = Proc.new { return 10 }


puts test_return(la) # 50
puts test_return(pr) # 顯示 LocalJumpError 錯誤訊息

Lambda 的 return 是從 Lambda return

Proc 則是從定義 Proc 的 scope return

講得很清楚,聽得很模糊嗎? 直接看 code 理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def test_proc
pr = Proc.new { return 10 }
result = pr.call
return result * 5
end

def test_lambda
la = lambda { return 10 }
result = la.call
return result * 5
end

puts test_proc # 10
puts test_lambda # 50

test_procpr.call 那行就 retrun 結束執行了,而 test_lambda 可以執行完方法中的每行程式。

Lambda 處理參數較嚴謹

Proc 處理參數較有彈性,而 Lambda 較嚴謹

1
2
3
4
5
6
7
8
9
10
11
12
13
pr = proc { |a, b| [a, b] }    # 也可寫成 pr = Proc.new{ |a, b| [a, b] }
la = lambda { |a, b| [a, b] } # 也可寫成 la = ->(a, b){ [a, b] }

p pr.call(5, 6) # [5, 6]
p pr.call # [nil, nil]
p pr.call(5) # [5, nil]
p pr.call(5, 6, 7) # [5, 6]


p la.call(5, 6) # [5, 6]
p la.call # 顯示 ArgumentError 錯誤訊息
p la.call(5) # 顯示 ArgumentError 錯誤訊息
p la.call(5, 6, 7) # 顯示 ArgumentError 錯誤訊息

Proc 針對參數的數量不會進行檢查,不足補 nil ,過多會自動丟掉;Lambda 會要求參數數量正確才會執行,較嚴謹,否則會顯示 ArgumentError 錯誤訊息。

Rails 的 scope 為什麼用 Lambda?

假設我們寫會帶入參數的 scope

1
scope :product_price, -> (type) { where(price: type) }

以 Proc 來做的話,Prodct.product_price 沒帶參數時,SQL query 依舊能夠執行,不會噴錯,因為 Proc 會將沒帶入的參數值預設為 nil ,在 SQL query 等同於執行 where(price: nil) ,會出現你預料外的狀況,在 Debug 會比較不好找。

反而 Lambda 能夠確保參數的數量正確性,過多或太少皆會 error 告訴你不能這麼做,避免不必要的狀況。

這也就是為什麼 Rails 中的 ActiveRecord model 在使用 scope 時,會用 Lambda 進行傳遞,原因是相比 Proc 來說,更為謹慎。

反而 Lambda 表現更像是常見的匿名函數。

使用 & 符號將 Block 與 Proc、Lambda 轉換

Block 轉成 Proc、Lambda

在 Rails 當中,假如我們要從資料庫找出所有使用者的姓名,利用 map 的話,寫法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Block 轉 Proc 範例
names = User.all.map { |user| user[:name] }
# 組成一個全都是姓名的 Array

# 也可寫成
names = User.all.map(&:name)
# 將 Block 轉 Proc


# Block 轉 Proc 範例
pp = Proc.new { |x| puts x * 2 }
[1, 2, 3].each(&pp)
# 原形 [1, 2, 3].each{ |i| pp[i] }
# 印出結果如下
2
4
6
=> [1, 2, 3]
# 印出結果如上


# Block 轉 Lambda 範例
lam = lambda { |x| puts x * 2 }
[1,2,3].each(&lam)
# 原形 [1, 2, 3].each{ |i| lam[i] }
# 印出結果如下
2
4
6
=> [1, 2, 3]
# 印出結果如上

那個奇怪的 & 符號代表帶入一個 Proc 或 lambda,將 Block 轉成 Proc 使用。

Proc 或 lambda 轉成 Block

剛才介紹 & 的其中一個用法,那就是在方法宣告同時,指定從 Block 轉成 Proc 或 Lambda,除此 & 還可以把 Proc 或 Lambda 轉成 Block:

1
2
3
hi_proc("Hahaha", &proc{ |s| puts s} )

hi_lambda = (1..5).map &->(x){ x*x }

當 Proc 或 Lambda 碰到 & 之後,會轉換成 Block,所以以上的示範意義與下相同:

1
2
3
hi_proc("Hahaha"){ |s| puts s }

hi_lambda = (1..5).map { |x| x * x }

&block 放參數最後面

Block 無法得知被物件化(參數化)後的 Block,需在最後一個參數前面加上 & ,這東西只能有一個,且必須放在最後面,否則會出現 syntax error。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 錯誤示範
def hi_block(&p, n)
...
end

def hi_block(n, &p1, &p2)
...
end


# 範例1
def hi_block1(str, &test01)
"#{str} #{test01.call(18)}"
end

hi_block1("Hello") { |age| "I'm #{age} years old." }
# "Hello I'm 18 years old."


# 範例2
def temp_b1
yield("參數1") # 小括號可省略
end

def temp_b2(&block)
block.call("參數2")
end

block1 = Proc.new {|x| puts "這是 Proc #{x}"}
block2 = lambda {|x| puts "這是 lambda #{x}"}


temp_b1 { |x| puts "block0 #{x}" } # block0 參數1
temp_b1(&block1) # 這是 Proc 參數1
temp_b2(&block2) # 這是 lambda 參數2

看完會發現 Ruby 中的 & 非常的神奇,背後做了很多事情,實際上它是生出一個 Proc 物件,雖然好用,但若不了解背後原理的話,會不知怎麼用、錯在哪。

有兩個以上 &block 該怎辦?

1
2
3
4
5
6
7
8
9
10
11
def two_block(n, p1, p2)
p1[n] # 等同於 p1.call(n)
p2.call n # 括號可省略
end

two_block('River', proc { |i| puts "#{i} 1" }, Proc.new { |i| puts "#{i} 2" } )
# 印出結果如下
River 1
River 2
=> nil
# 印出結果如上

建立一個 Proc 物件,並當參數傳入即可,但還是得在建立同時寫 Block 給 Proc.new 方法。乍看之下很冗長又不好看,當想同時傳入多個 Block 作為參數時,適用此技。

小結

這篇很燒腦,找蠻多資料參考,從一開始撰寫時不太清楚,到後來能解釋,過程中有感覺變強一些。

寫不清楚或錯誤部分,歡迎提出討論。


參考

  1. 為你自己學 Ruby on Rails
  2. Ruby 探索:Blocks 深入淺出
  3. Ruby Block, Proc and Lambda
  4. method / block / yield / Proc / lambda 全面解釋
  5. Build, Break, Learn.
  6. What does map(&:name) mean in Ruby?

原文連結:https://riverye.com/2019/11/15/Ruby-中的-Block、Proc、Lambda-是什麼?/

發表日期:2019-11-15

更新日期:2022-09-23

CATALOG
  1. 1. Block (程式碼區塊)
    1. 1.1. 什麼是 Block ?
    2. 1.2. Block 中 花括號 {} 與 do..end 差異
      1. 1.2.1. 除此之外,還有別的差異嗎?
    3. 1.3. 如何執行 Block 的內容?
    4. 1.4. 傳參數給 Block
      1. 1.4.1. yield 後面可以帶 1 個或以上的參數
      2. 1.4.2. yield 進階使用
    5. 1.5. Block 的回傳值
      1. 1.5.1. Block 最後一行的執行結果也會自動變成 Block 的回傳值
    6. 1.6. Block 不是參數
    7. 1.7. 怎判斷有無 Block?
      1. 1.7.1. 可以使用 Ruby 提供的一個判斷方法 block_given?
    8. 1.8. Block 特性
      1. 1.8.1. 雖然 Block 不是物件,不能單獨存在
      2. 1.8.2. 但 Ruby 有內建兩個方法使 Block 物件化且單獨存在: Proc 和 Lamda
  2. 2. Proc
    1. 2.1. Proc 中不能加入 return
    2. 2.2. Proc 可帶參數
    3. 2.3. Proc 呼叫方式
  3. 3. Lambda
    1. 3.1. Proc、Lambda 怎麼分
      1. 3.1.1. Proc 和 Lambda 一樣都是屬於 Proc 物件
    2. 3.2. Lambda 裡可加入 return
    3. 3.3. Lambda 處理參數較嚴謹
    4. 3.4. Rails 的 scope 為什麼用 Lambda?
  4. 4. 使用 & 符號將 Block 與 Proc、Lambda 轉換
    1. 4.1. Block 轉成 Proc、Lambda
    2. 4.2. Proc 或 lambda 轉成 Block
    3. 4.3. &block 放參數最後面
    4. 4.4. 有兩個以上 &block 該怎辦?
  5. 5. 小結
  6. 6. 參考