Ruby語法糖衣: Symbol#to_proc 如何運作的?

Posted by JSON on March 20, 2015
# Ruby語法糖衣讓我們可以將
word.map { |s| s.length }
# 寫成
word.map(&:length)

所以&:symbol幫我們做了什麼事情?

先了解兩件事實

  • & 會對緊接在後的 物件 執行 to_proc 方法。
  • .map 需要吃block或proc作為參數
obj = Object.new
(1..10).to_a.map &obj     
# 引發錯誤 TypeError: wrong argument type Object (expected Proc)
# 表示&嘗試對obj做to_proc,但因為obj沒有to_proc這個方法而將自己回傳,map因此收到了一個非法物件

接下來試著對obj塞入to_proc方法:

obj.define_singleton_method(:to_proc) do
     proc {}
end
(1..10).to_a.map &obj  # => [nil, nil, … ]

這一次沒有錯誤了,現在再做一點小改變:

obj.define_singleton_method(:to_proc) do
     proc { |input| input * 2 }
end
(1..10).to_a.map &obj  # => [2, 4, 6, 8, 10, ...]

看懂了嗎?

&obj 物件透過 to_proc 轉換成 proc 物件,送給 map 方法,接著迭代每一個物件作為 proc 的參數並執行。

所以我們一開始的問題 &:symbol 解答了。 &:symbol 物件透過 to_proc 轉換成 proc

最後就不難看懂為了麼 word.map(&:length) 可以達到我們想要的效果:

class Symbol
     def to_proc
          proc { |obj| obj.send(self) }
     end
end

這只是為了讓你了解Symbol#to_proc做了什麼事, 並不是實際上Symbol Class的code, 但是你可以用這段代碼複寫原本的Symbol#to_proc,並不會改變原來的行為。

最後附上Ruby Symbol#to_proc 的source code,由於是在C內實作,所以看不到body的部分,但可參考其註解:

class Symbol
     # sym.to_proc
     # # Returns a _Proc_ object which respond to the given method by _sym_.
     # # (1..3).collect(&:to_s) #=> ["1", "2", "3"]
     def to_proc()
          #This is a stub, used for indexing end
     def     
end