iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 19
0
自我挑戰組

滿滿的紅寶石不拿嗎?-- 去吧!我把世界上的一切都放在那裡了! 系列 第 19

Proc & Lambda <> 通通都拿出來變成實用的武器吧! - 滿滿的紅寶石不拿嗎?

[Day19]「吃下惡魔果實,什麼 Block 都能變成物件!」

不得不說尾田老師真的是常常腦洞大開,居然還有吃了狗狗果實的槍還有吃了象象果實的劍!

前幾天提到了 Block 不能在程式裡單獨存在,不過,今天要介紹的 ProcLambda ,在某些地方又被稱為「匿名函數」,它們可以幫忙把 Block 拿出來變成物件使用。

Proc 是什麼?

簡單來說,Proc 是一種「物件化」的 Block,如果稍微忘記物件的朋友可以先坐時光機回到 第 10 天 再回來繼續看喔!

物件化?具現化?


為什麼要把 Block 物件化變成 Proc?

從物件導向的角度切入,當我們需要 Block 可以被傳遞,並作為一個 receiver 來接受訊息&使用方法,這時候就會用 Proc 對 Block 進行物件化,我們直接看程式碼吧!

一般的方法只能被呼叫:

def add_two()
    2 + n
end

add_two(3)

但如果透過 Proc.new 產生一個 Proc 物件,就可以呼叫其他方法了:

add_two = Proc.new { |x| x * 2 }
add_two.call(3)

除了呼叫方法之外,也能在程式裡被傳來傳去了!變成了 Proc 以後真是好處多多啊!

list = [1, 2, 3, 4, 5]

double = Proc.new { |x| x * 2 }
p list.map { |element| double.call(element) }

# 印出
[2, 4, 6, 8, 10]

使用 Proc 不小心忘了寫 new

好!現在我們已經知道要用 Proc.new 把一個 Block 物件化:

double = Proc.new { x * 2 }

# 印出
#<Proc:0x00007fa4592659a8> 

有時候可能會忘了 new ,不小心寫成 proc

double = proc { x * 2 }

# 印出
#<Proc:0x00007fa459284e48> 

別擔心這樣也可以!


Lambda 是什麼?

接著要介紹一個和 Proc 很像的東西:Lambda ,它的寫法是這樣:

double = -> { x * 2 }

#<Proc:0x00007fa4598f5de0 (lambda)> 

如果 Javascript 寫習慣了想用 -> 也行:

double = -> { x * 2 }

# 印出
#<Proc:0x00007fa45a0acac0 (lambda)> 

Ruby 還會貼心提醒你這是個 lambda :)


LambdaProc 有什麼不同?

兩個真的很像吧?Lambda 的效果就和 Proc 一樣,都能把 Block 變成一個獨立的物件,但兩者還是有差異的,否則幹嘛要設計兩個名詞自找麻煩呢?

LambdaProc 的差異主要有二:

  • 回傳值的情況不同
  • 參數判斷的方式不同

LambdaProc 回傳值的方式不同

這邊借五倍的範例來做解說:

def double(callable_object)
  callable_object.call * 2
end

la = lambda { return 10 }
pr = proc { return 10 }

double(la) 
=> 20
puts double(pr) 
=> LocalJumpError (unexpected return)

從以上結果發現 Lambda 可以被當作帶入方法的參數,而 Proc 就無法辦到,但這並不是 Proc 不能執行或沒有回傳值,而是因為 Proc 在回傳值時,是從定義 Proc 時的 scope 回傳。

嗯...什麼意思?

再繼續看這個:

def lambda_double
  la = lambda { return 10 }
  result = la.call
  return result * 2
end

def proc_double
  pr = Proc.new { return 10 }
  result = pr.call
  return result * 2  # unreachable code!
end

現在把 LambdaProc 放在方法裡,我預期 lambda_doubleproc_double 應該都會得到 20 這個結果,但事實上:

lambda_double
=> 20

puts proc_double 
=> 10

我們從這個例子可以更明顯地看到 LambdaProc 的不同,Lambda 的回傳值可以離開 Lambda 本身給其他程式碼使用,但 Proc 就只能在一開始定義這個 Proc 的那行回傳。


LambdaProc 參數判斷的方式不同

如果我們給 Proc 的參數數量不對:

pr = proc {|a,b| [a,b]}
pr.call('a', 'b')
=> ["a", "b"]

pr.call('a')
=> ["a", nil]

pr.call('a', 'b', 'c')
=> ["a", "b"]

可以發現,如果參數給的比預期多 Proc 會自動忽略;如果比預期少的話,則會補一個 nil,基本上都還是可以動,這點和 JavaScript 蠻像的!

相對來說,Lambda 在參數數量這點上就比較嚴格,數量必須要給正確才行:

la = lambda {|a,b| [a,b]}
p la.call('a', 'b')
=> ["a", "b"]

p la.call('a')
=> ArgumentError (wrong number of arguments (given 1, expected 2))

p la.call('a', 'b', 'c')
=> ArgumentError (wrong number of arguments (given 3, expected 2))

在前面加 &?這是什麼?

最後來看一個比較特殊的情形,先直接看 code:

list1 = [1, 2, 3, 4, 5]
list2 = ["a", "b", "c"]
double = -> (x) { x * 2 }

p list1.map(&double)

# 印出
[2, 4, 6, 8, 10]

咦咦咦!這什麼?
第一次看到時,差點以為要被毀三觀,更冒出一堆黑人問號:

  • map 後面不是要加 Block 嗎?
  • double 在這裡不是一個 Lambda 物件嗎?
  • 怎麼可以像是參數一樣在傳呢?
  • 前面那個& 又是什麼?

在經過一番消化理解後,才發現原來在這裡是把 double 這個Lambda 物件前面如果加上了 &,就能又轉成了 Block 接在方法後面使用(寫法卻已經大大不同了!)真的是翻了很多文章才看出一點端倪(盯)

今天翻閱參考文章時,還有看到一個很特別的用 & + symbol 的寫法!我花點時間弄清楚後會再貼上來分享給大家!


結語

在 Javascript 的世界裡,function 具有 higher order 的特性,因此可以被當作參數丟來丟去,不過在 Ruby 的世界裡就沒有此特性,必須先寫成 ProcLambda 才能達成,不過其實還好,因為 Ruby 方法已經很好用了,每個語言本就有各自的強項和弱項,這和程式語言的設計哲學有很大的關聯。

呼~今天這篇真的是花很多時間在寫,都沒時間找梗圖了!希望自己對 Proc 還有 Lambda 的理解還算正確,如有說明不清楚的地方,歡迎大家留言在下方或給我一些寫作上的建議,感謝!


上一篇
scope <> 你已經進到「死亡外科醫生」的領域了!ROOM! - 滿滿的紅寶石不拿嗎?
下一篇
開放類別 <> 這個星期的我很可以!不管裡面有什麼都給我來一點吧!- 滿滿的紅寶石不拿嗎?
系列文
滿滿的紅寶石不拿嗎?-- 去吧!我把世界上的一切都放在那裡了! 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言