在 Ruby 世界中有著區塊(本篇將稱 block)的特殊語法,跟語言的執行程序上有大大的關聯,今天就來好好跟大家介紹這些部分吧!

區塊 Block

前面有跟大家說到 ruby 幾乎甚麼都是物件,但唯獨 block 這個東西他不能算是物件,而
block 在 ruby 中有兩種:

  • {} 之內
  • do…end 的範圍內

Block 的特性

總結有以下的特性:

  • 一段不會被主動執行的程式碼
  • 不是物件
  • 像寄生蟲一樣需要依附在某些方法或物件,且不是參數
    Block 會不會執行,要看宿主臉色,是否有 yield

可以看下面的例子,我們定義一個吃東西的方法,food 是參數,下面的兩種 block 類型其實都不會被執行,既使看起來有被呼叫到,就像我們上面說的特性,不會被主動執行,也不是參數。

1
2
3
4
5
6
7
8
9
10
11
12
def eat_something(food)
# eat something here
end

eat_something(rice) {
puts "Block在這"
}

# 或是 do ... end 寫法
eat_something("noodle") do
puts "這裡也是Block"
end

如何執行? (yield 控制權轉讓)

不知道大家有沒有在馬路上看過這樣的標誌?

大家應該知道這樣的號誌出現就是行車要禮讓行人的路口,等行人過去之後才能繼續前進。
其實 block 的運作就是像是這樣的情境喔!!
當出現 yield 的時候,區塊內的程式碼就會馬上被執行,而這樣的狀況其實是控制權的轉讓,當執行完成後再交出控制權回去依序執行剩下的程式碼。

舉個例子來說:
下面個別字串後面括號的順序就是執行的順序喔! 可以看到 yield 表示將控制權交給 block { } 內的程式碼

1
2
3
4
5
6
7
8
9
10
def drive_car
puts "行駛中!!(1)"
yield
puts "我是好公民要等行人過完才能走喔!!(3)"
end

drive_car {
puts "停車!--這裡是block喔--(2)"
}
puts "可以走啦!!(4)"

傳遞參數給 Block

Block 不是參數,但可以做為傳遞參數的角色。
轉讓的同時,還可以帶上拌手禮傳遞參數,依照傳遞的多寡可以依序轉讓。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def block_pass_one
yield 10 # 傳遞10出去
end
block_pass_one do |n| # do..end 之間接到參數並指定給 n
puts n # 印出 10
end

def block_pass_two
yield 10,20 # 傳遞10, 20出去
end
block_pass_two do |n, m| # do..end 之間接到參數並依序指定給 n, m
puts n # 印出 10
puts m # 印出 20
end

如果沒有 Block 但卻 yield 的話?

會出錯喔!! 控制權轉讓之後沒有 block 可以接的話就會有 localJumpError。
def say_hello # 轉讓控制權,往下找到 test two 的方法
yield
end
say_hello # no block given (yield) (LocalJumpError)

block 回傳值

Block,不用寫 return,最後一行的執行結果會自動變成回傳值。像是一些方法的使用可以看出來,如下面這些例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# map 與 select 方法 {} 之間的運算就是最後的回傳。
p (1..10).map{|x|x*2}

p (1..10).select{|x|x.odd?}

# test two 方法後面接上block,{}內的判斷就成為最後的回傳
# yield後面帶入的參數會影響下方{}內判斷結果,再回到原本方法去判斷是要印出哪個內容
def test_two
if yield(2)
puts "yes, it is 2"
else
puts "no, it is not 2"
end
end

test_two {|n| n == 2 }

如果使用 return 在 block 中呢?

也會出現 localjumperror,因為 Block 不是一個方法,不知道 Return 到哪裡去,所以造成錯誤。

1
p (1..100).select{ |x| return x % 2 == 0 }

do…end 及 大括號 {} 之間差異?

一般來說是一樣的,但可能會有優先度的差別,在一些方法的寫法上導致類似先乘除後加減的狀況。

  • Block 的內容如果有多行,會建議使用 do .. end 寫法
  • 如果只有一行,則建議使用大括號寫法
1
2
3
4
5
6
p [*1..10].map { |i| i * 2 }
# => 得到 [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] {} 優先度高

p [*1..10].map do |i| i * 2 end
# => 得到 <Enumerator: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:map> do...end 優先度低,
# 可以還原成 (p [*1..10].map) do |i| i * 2 end,括號內先執行完,所以後面的條件沒有被帶入執行。

以上就是基本對於 block 相關的介紹,後面可以開始進入 ruby 這充滿物件的世界中-物件導向,了解一下關於物件導向的一些概念吧。


參考資料:

  1. 為你自己學 Ruby on rails