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。