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本身的概念很單純,當初困擾我的是常常在解說的時候會一起出現的yieldproc


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

Symbol#to_proc

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 }