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

就像是…

yield

剛剛 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

  1. 任務指示一開始要去找 NPC yield
  2. 找到 NPC yield: 你拿著這個素材 24 先去完成 block 裡的內容(執行支線任務)。
  3. 你拿著素材 24 執行了支線裡的內容 puts "#{素材} yard-pass"
  4. 回過頭去完成 puts "first down!"
  5. 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" }, 這裡是這樣運作的:

  1. x = parameter, 將參數指定給區域變數 x (當然可以自定變數名稱)
  2. put x in ||
  3. 如果是字串,使用 #{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。

Reference