Proc是甚麼可以吃嗎
Proc是甚麼可以吃嗎
雖然主題是Proc但是…..
先別管proc了,你有聽過block嗎?
Block
簡單的說就是do…end或是{ …. }這種東西
#block1號
[1,2,3].each do |i|
p i
end
#block2號
[1,2,3].each { |i| p i }
上面那兩種是一模一樣的東西,但通常大家習慣如果只有一行的時候寫成{…},但要把他分行也是可以正常執行的。
block無法單獨存在也沒有辦法代入變數,所以如果寫成下面這樣子,ruby是會黑人問號的
#企圖單獨存在的block
do
puts "我是block"
end
#Main.rb:1: syntax error, unexpected keyword_do_block
#Main.rb:3: syntax error, unexpected keyword_end, expecting end-of-input
#企圖丟給變數的block
block = { puts "我是block" }
#Main.rb:1: syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '('block = { puts "我是block" }
^
#Main.rb:1: syntax error, unexpected '}', expecting end-of-input block = { puts "我是block" }
block本身的概念很單純,當初困擾我的是常常在解說的時候會一起出現的yield和proc
yield / &
但是,當初我到底是哪裡看不懂呢?
比方說可能常常看到講解會用像下面的範例來和你說明碰到yield的時候會先跑去block那邊處理
def i_am_method
yield
end
i_am_method do
p 'this is a block'
end
#上面執行會得到this is a block
BUT!!
其實…………..我的腦袋沒有辦法理解上面這段程式orz
我不能理解為什麼yield會知道他是要執行甚麼block,又沒有傳東西給他?!?!
因為平常執行method的時候大概會長的像下面這樣
def say_hello(name)
p "hello~#{name}"
end
say_hello('telsa')
#會得到"hello~telsa"
總之會很明確知道你現在傳了甚麼東西進到method裡面,但上面的yield沒有,那他怎麼知道的?!
那讓我們來換一種寫法
def i_am_method(&block)
block.call
end
i_am_method do
p 'this is a block'
end
#上面執行還是會得到this is a block
這樣子就可以明確知道block是真的被傳給了iammethod,而且在裡面被呼叫,
和上面用yield本質上是一模一樣的,但yield似乎比較多人用。
另外一個method只能吃一個block而已,而且一定要放在最後面
比方說你沒辦法寫成像下面這樣
def i_am_method(&block1, &block2)
end
def i_am_method2(&block, value)
end
這也是為什麼用yield的時候不明確寫出是要執行哪一個block也可以的原因,因為反正一個method只能接受那一百零一個block,就算不明講,ruby也知道要去call哪個block,那當然大家也就會選寫起來字數比較少的那一種寫法。
但對當初不知道這一段的我來說,yield是很謎的的東西。
只是還剩下一個問題,剛剛那個謎樣的 & 是甚麼?
簡單的說就是會幫你把block轉成proc。 不然你是沒辦法把block丟給method的。
(反過來說&後面要接的必須是block,像&3這種東西是會噴錯的)
於是終於可以進到文章的主題了。
Proc
前面有提到過block本身沒有辦法單獨存在,但是透過proc把他變成物件就可以了。
下面幾種方法都是把block變成proc。
Proc.new { |i| i * 2 }
proc { |i| i * 2 }
lambda { |i| i * 2 }
->i{ i * 2 }
#<Proc:0x000055d6891564d0@Main.rb:1>
#<Proc:0x000055d6891563b8@Main.rb:2>
#<Proc:0x000055d6891562a0@Main.rb:3 (lambda)>
#<Proc:0x000055d689156200@Main.rb:4 (lambda)>
變成proc之後它們就可以被代入變數,丟給method的時候也不需要在前面加上&(畢竟加&本來就是為了要把block轉成proc,如果本來就已經是proc了,當然就不需要了)。
def i_am_method(proc)
proc.call
end
proc = Proc.new do
p 'this is a proc'
end
i_am_method(proc)
#會得到"this is a proc"
如果沒有proc, 上面那些範例們也就不會動了。
proc哪邊最常看到呢?
像下面這個樣子,也是當初我覺得很謎的寫法。
%w(A B C).map(&:to_sym)
#會得到[:A, :B, :C]
謎樣的符號&又出現了,最一開始我還是以為那個是"&:“+method名。
後來才發現他其實是&+symbol。
雖然這樣寫起來很帥又變短了,但這到底是甚麼妖術?
完整寫法是像下面這樣:
%w(A B C).map{ |i| i.to_sym}
按照前面幾段對&的解釋,應該已經知道ruby會嘗試把&後面的東西轉成proc。
可是現在後面接的是symbol阿? 如果&後面只能吃block理論上應該會噴錯才對?
是因為有下面這個method
Returns a Proc object which respond to the given method by sym.
class Symbol
def to_proc
lambda { |obj, *args| obj.send(self, *args) }
end
end
拆開來的話他其實幫你做了下面這些步驟,所以我們就可以用它來讓程式變短了。
#原始
["A","B","C"].map(&:to_sym)
#把他帶到上面的to_prco,外加map只能吃一個block參數,所以變成下面這樣
["A","B","C"].map{|obj| obj.send(:to_sym) }
#然後幫你叫你傳給他的symbol相對應的method
["A","B","C"].map{|obj| obj.to_sym }