Chapter 4: Blocks - Part III

” Blocks are used for passing blocks of code to methods, and procs and lambda’s allow storing blocks of code in variables.” ― App Signal ―

可以調用的object稱作callable object。

Callable Objects

簡單來說 callable object 就是『 可以調用的物件 』。 先從書中的 “package code first, call it later” 開始說起。當我們使用 block 時,其實是經過兩個步驟的過程:

  1. 會先把程式碼打包在 block 裡,
  2. 接著第二步, yield 之後才能被執行。

像這樣 “先打包後執行” 的機制在 Ruby 的世界裡,至少有三種物件也是如此:

Proc Objects

也許你已經聽過了很多次 “Everything in Ruby is object”,這句話對 blocks 來說可能會感到被冷漠了,因為對 Ruby 來說 blocks 就不是物件。那當 object 是有什麼好處呢?其中之一對 block 而言,就是能夠被儲存在 object 裡,不需要再依附在 method 身上,也不用再等 yield 才能夠被執行。

也許是 Ruby 聽到了 block 的哭泣,Ruby 在標準函式庫裡提供了 Proc 類別。Proc 基本上就是物件化的 block,你可以用 Proc.new() 方法來建立它的實例物件,呼叫它的時候則可以用 Proc#call() 方法。

add_two = Proc.new { |x| x + 2}
add_two.call(2)   # => 4   

Ruby 提供兩種 Kernel 方法能夠將 block 物件化:(1) lambda and (2) proc

Lambda Objects

# 建立 lambda 的方式有兩種:
# 第一種寫法:
add_two = lambda { |n| n + 2 }

# 第二種寫法:
add_two = -> (n) { n + 2 }

# 呼叫 Lamda 的方法有四種:
add_two.call(2)   # => 4
add_two[2]        # => 4
add_two.(2)       # => 4
add_two.===(3)    # => 5

# 建立 proc 的方式有兩種:
# 第一種寫法:
add_two = Proc.new { |n| n+2 }

# 第二種寫法:
add_tw = proc { |n| n+2 }

# 呼叫 proc 的方法有四種:(與lambda相同)
add_two.call(2)   # => 4
add_two[2]        # => 4
add_two.(2)       # => 4
add_two.===(3)    # => 5

Lambda VS Proc

既然 blocks 都可以透過 lambda 和 proc 方法來物件化,那它們之間有差別嗎? 主要的不同之處有兩個地方:

  1. return 的方式:在 procs 的裡面如果放上 return 關鍵字,程式會在 procs 本身的作用域 (scope) 裡執行 return 就結束了。換句話說,procs 不管以下還有沒有程式碼,都不會繼續往下讀取。相比之下,lambdas 則是會將 return執行完畢後,再繼續跑之後的程式碼。 https://ithelp.ithome.com.tw/upload/images/20201001/20120868SY0XWGZM2C.png

  2. 處理引數的方式:procs 不會對引數的數量錯誤而爆出錯誤訊息,它會默默地在不足的地方補上 nil 值。相對之下,lambdas 就比較接近 method 的用法,如果引數與定義方法上的參數的數量不符合,則會爆出 ArgumentError 給你。 https://ithelp.ithome.com.tw/upload/images/20201001/20120868ij7UNiFFHz.png

Method Objects

方法(method) 也可以像 procs 或是 lambdas 這樣 “先打包後執行”,看看以下程式碼:

class MyClass
  def initialize(value)
    @x = value
  end
  def my_method
    @x
  end
end

object = MyClass.new(1)

m = object.method :my_method
m.class        # Method
m.call         # => 1

在呼叫 Kernel#method() 方法後,my_method() 方法就被物件化的為 Method object,之後如需呼叫時,也是用 Method#call() 方法來執行。實際Method object 是與 block 或是 lambda 類似,更確切的說,你可以做以下這些轉換:

Method#to_proc:把 Method 轉為 Proc Module#define_method:把 Block 轉為 Method

注意:Methods 與 lambdas 的差別在於 Method 的作用域(scope)是在本身定義的 object 上。然而,lambda 的 scope 則是在定義的區域上。

The & Operator

& 符號可以把 block 轉為 Proc ,也可以從 Proc 轉回 block。 以下面程式碼為例: https://ithelp.ithome.com.tw/upload/images/20201001/20120868T5ai7ABWiq.png

  • 第15行: 呼叫 do_math() 方法時會帶引數 23,以及 {|x, y| x * y},傳進去給第5行定義好的do_math。
  • 第5行: 會依順序將引數收下來( a=2, b=3),遇到 & 符號時,原本的 block { |x, y| x * y } 就會轉為 Proc。

  • 第7行: 可以很明顯看出已經轉換成 Proc 類別了。

  • 第11行: 呼叫 math() 方法時會帶引數 ( a=2, b=3 ),遇到 & 符號時,原本的 Proc 就會轉為 block。