Block
Ruby 為物件導向程式語言,幾乎所有東西都是物件,但 Block 卻不是,那 Block 是什麼?
Block 指的是 Code block 程式碼區塊,以 { }, do...end 呈現。通常單行程式碼使用 { }, 多行則使用 do...end。
而在選擇使用 { }, do...end, 會影響程式執行的優先權,像是數學的先乘除後加減規則:
p ([*1..10].map { |num| num * 2 })
# { } 優先權高,正常執行 { } 內容
p ([1..10].map) do |num| num * 2 end
# 相反地,do...end 裡的程式碼不會被執行
How to call Block?
Matz: any method can be called with a block as an implicit argument. Inside the method, you can call the block using the yield keyword with a value.
Ruby 的阿爸:Block 可以當作隱式參數傳進任何方法。在方法裡使用 yield keyword 去呼叫 block。=> Block sticks with method, may appear only in the source adjacent to a method call.
Use yield:
yield 將執行權暫時交給 Block 執行
def countdown
puts 3
yield # 先去執行 { puts 2 }, 回來繼續往下
puts 1
end
run_order { puts 2 }
# => 3
# => 2
# => 1
好玩的是,你可以一直呼叫
def countdown
puts 3
yield
puts 1
yield
end
run_order { puts 2 }
# => 3
# => 2
# => 1
# => 2
就像是…

剛剛 Ruby 的老杯其中有一段 ‘ using yield with value ‘:代表使用 yield 可以傳值(參數)給 Block,
def get_1st_down
yield 24
puts "first down!"
end
get_1st_down { |x| puts "#{x} yard-pass" }
# => 24 yard-pass
# => first down!
就像解任務般:任務 get_1st_down
- 任務指示一開始要去找 NPC
yield, - 找到 NPC
yield: 你拿著這個素材24先去完成 block 裡的內容(執行支線任務)。 - 你拿著素材
24執行了支線裡的內容puts "#{素材} yard-pass" - 回過頭去完成
puts "first down!" - Quest Clear!!
那怎麼去驗證 NPC yield 有沒有唬爛? 使用 Ruby 內建方法 block_given?,
# 使用 block_given?
def is_block
yield if block_given? # return true then execute
end
is_block # => nil
is_block { p "You got me." } # => You got me.
Block Varaiables
上面提到可以將參數傳進 block,{ |x| puts "#{x} yard-pass" }, 這裡是這樣運作的:
x = parameter, 將參數指定給區域變數 x (當然可以自定變數名稱)- put x in
|| - 如果是字串,使用
#{x}將 x 傳入
想想看這會印出什麼?
x = 10
5.times do |x|
puts "x in block: #{x}"
end
puts "x out of the block: #{x}"
The output is:
=> x in block: 0
=> x in block: 1
=> x in block: 2
=> x in block: 3
=> x in block: 4
=> x out of the block: 10
從上面可以觀察到,儘管一開始區域變數 x = 10 已經先設定好, 但 block 只找到自己的 block varaiable |x|,而最後一行才去找到 block 外面的 x = 10。
Practical examples of Block
最後再來看看幾個實例,
yield:
def run_time
Start = Time.now
yield
Time.now - Start
end
run_time { 'a' * 10000000 }
做一個方法 run_time:設定 Start 為現在的時間 => yield 會暫時去執行 block => 紀錄執行完的時間,並減去開始的時間 => 使用 run_time 方法並掛載 block
Lazy code:
{ a: 1 }.fetch(:a) { p "no such value" } # => 1
{ a: 1 }.fetch(:b) { p "no such value" } # => no such value
{ a: 1, b: nil }.fetch(:b) { p "no such value" } # => nil
使用 fetch 方法抓 Hash 裡 :a 的值,如果沒有該值便會印出後面 no such value,比較像是設定 Default 的使用。(注意就算是 nil 也是有值)
Callback:
def http_request(&on_complete)
# ...
on_complete.call
end
http_request { puts "Done" }
最後就是將 block 實體化的例子了。
最一開始提到 Block 不是物件,但 Ruby 卻提供 Proc, Lambda 可以將 Block 物件化 ……哪泥!!
下篇將會解釋解釋物件化後的 Block。