iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
4

名字裡究竟有什麼?如果玫瑰不叫玫瑰,它還是一樣的芳香。

-- 莎士比亞, 哈姆雷特


雨,開始下起來了。空氣中滿是潮溼的香氣,隨著雨滴打在道路及建築上的聲音漸響,萬事萬物,開始靜默下來。

無視雨打進窗沿,坐在我對面的那隻小動物,依然悠悠的一邊晃著尾巴,並在桌上刻出各種範例,一邊講訴著故事…


但是在這個 JS 莊園裡,從最初開始,函式就是一等公民。設計者喜歡他曾待過的 LISP 群島中的 Scheme 島。於是在設計這個莊園時,就將這個傳統帶進來了。在這裡,函式可以指派給變數

let cmp = function(i1, i2) { return i2 - i1 }

接下來就可以把這個函式,直接傳給sort 函式做為參數

[1, 4, 3, 6, 5, 2, 7, 8].sort(cmp) //=> [8, 7, 6, 5, 4, 3, 2, 1]

當然,如果沒有其它地方需要用到那個 cmp 函式的話,你也可以不要指派變數,而是在呼叫 sort 函式時,直接把匿名函式傳遞進去:

[1, 4, 3, 6, 5, 2, 7, 8].sort(function(i1, i2) { return i2 - i1 }) //=> [8, 7, 6, 5, 4, 3, 2, 1]

而在上一次莊園都更的時候,更引進一種新的函式的鍊成陣。你可以用這種方式做出匿名函式:

function (i1, i2) { return i2 - i1 } // 把這個

(i1, i2) => i2 - i1 // 改寫成這樣

數列的排序也可以改寫成這樣:

[1, 4, 3, 6, 5, 2, 7, 8].sort((i1, i2) => i2 - i1) //=> [8, 7, 6, 5, 4, 3, 2, 1]

詠唱的方式

你有沒有注意到,我們今天畫出來的函式鍊成陣,跟我們昨天畫出來的函式,除了內容之外,還有個地方不太一樣?

function name(x) { /* 做一些事 */ } //之前的

function (x) { /* 做一些事 */ } //今天的

「少了那個 name,少了…名字」。

沒錯!函式有名稱這個屬性。但既然它是自由的,它也可以不要有名字。事實上,在許多不同地方,對於這種沒有名字,且是一等公民的函式,有一種共同的稱呼,叫做:「嵐達 (lambda)」。而既然它沒有名字,也有人把它稱為匿名函式

也有許多魔法師,認為它的真名,是一個符號: https://chart.googleapis.com/chart?cht=tx&chl=%5Clambda


再一層的…抽象

我們再來試個複雜一點的術法,首先假設我們有這樣的兩個術式,有看到它們之間類似的地方嗎?

// 第一段
putInPot("beef")
putInPot("soup")
console.log("beef soup is done!")

// 第二段
boomboom("chicken")
boomboom("coconut")
console.log("chicken coconut is done!")

// 被呼叫的函式定義在這
function putInPot(igr) {console.log(igr + " in the pot!")}
function boomboom(igr) {console.log("boil the " + igr + "!")}

「兩個的過程很像,都是呼叫兩次函式,然後用 console.log 印一段使用了同樣參數的句子。」

是的,那我們有辦法再繼續簡化這兩段術式嗎?

過去的 JAVA 王國魔法師會認為這段魔法沒有辦法再更簡潔了,或是要動用到鏡反射 (reflection) 等麻煩的東西才搞得定。

但是正如你說的,施法的過程是一樣的,只是呼叫的函式不同而己,而函式,也可以當做參數。有了這層體認,我們就可以把這個呼叫兩次函式,然後印一段句子過程抽象出來:

function cook(igr1, igr2, f) {
  f(igr1)
  f(igr2)
  console.log(igr1 + " in " + igr2 + "is done!")
}

而想要真正調用魔法時:

cook("beef", "water", putInPot)
cook("chicken", "coconut", boomboom)
// 加上原先 putInPot 跟 boomboom 的實作

有注意到我們呼叫 cook 函式時的最後一個參數,putInPotboomboom 的後方沒有加上 () 符號。那是因為在那裡我們並不想要調用這些函式,而是把函式本身當做參數傳給 cook。真正調用這些函式的地方,在 cook 函式裡的 f(igr1)f(igr2) 那裡。

當然,我們也可以選擇不要把 putInPotboomboom 這兩個函式分開來定義,而是直接寫進調用 cook 的過程裡:

cook("beef", "water", igr => console.log(igr + " in the pot!"))

cook("chicken", "coconut", igr => console.log("boil the " + igr + "!"))

這整段改寫的過程,讓我們更能體會這樣一句話:函式是計算過程的容器


「可是小浣熊,你說的這些,我都曾經用過啊。那個匿名函式,不就是 callback 嗎?那些我早就知道了啊。」終於忍不住了。

我知道的。一直以來你都蠻會使用的,不然就不會召喚到你了。但是啊…蘋果,是水果、是食材、是果實,也是類似球狀的物體。差別在於觀點的不同。

而用不同的角度來理解同一件事,會讓我們獲得更為深刻的洞見。記得我們上次提到的三個特性嗎?如果沒有深刻的理解一等公民的概念,最後一個,也是最有威力的魔法,施展起來會非常不順手。

「什麼最後一個魔法?」

回傳函式的函式。

啊,還有,我不是浣熊

[to be continued]


上一篇
mostly:functional 第六章:王國的改革、觀點的困境
下一篇
mostly:functional 第八章:急躁的,耐心的,以及還不完整的。
系列文
mostly:functional 從零開始的異世界程式觀 --- 函數式程式設計的試煉35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1

你是小貓熊

看更多先前的回應...收起先前的回應...

欸這留言壞了吧 還沒辦法刪的樣子

taiansu iT邦新手 4 級 ‧ 2020-09-22 13:24:27 檢舉

/images/emoticon/emoticon04.gif

taiansu iT邦新手 4 級 ‧ 2020-09-22 13:26:48 檢舉

卡米狗不要這麼早就爆雷啊 XD

XDDDDDDDD

0

你是小貓熊

XDDDD

0

你是小貓熊

0

你是小貓熊

0

你是小貓熊

0
微笑
iT邦研究生 5 級 ‧ 2020-09-22 18:24:48

文章標題好酷澳

看更多先前的回應...收起先前的回應...
taiansu iT邦新手 4 級 ‧ 2020-09-22 22:06:45 檢舉

謝謝你!/images/emoticon/emoticon41.gif

微笑 iT邦研究生 5 級 ‧ 2020-09-23 09:01:23 檢舉

昨天原本還看不懂這個用法跟callback的差異
剛剛騎車時忽然想到

所以callback不是一個function的return替代式
是將function寫入另一個function做呼叫呀

taiansu iT邦新手 4 級 ‧ 2020-09-23 11:21:17 檢舉

喔喔很接近了,callback 有個正式的稱呼叫 Continuation-passing style (CPS),是指某個函式裡會呼叫另一個函式,但是這個被呼叫的函式是用「參數」的方式傳進來的。

而這個參數可以另外定義,或是用變數的方式傳進來,也可以在呼叫的時候直接寫在呼叫當下。

用數字來看:

function addOne(i) {
  return i + 1; //這裡用到了 i
}

let x = 100;
addOne(x); //可以像這兩行

addOne(100); //也可以像這樣

那麼函式也一樣,只要傳遞的參數是個函式,這兩種寫法都算是 callback。但是用 functional 的方式想就不用糾結在這些名詞上了。反正函式可以當成參數,範例跟 map, reduce, filtersort 通通是同一種東西。

微笑 iT邦研究生 5 級 ‧ 2020-09-23 14:17:08 檢舉

你好熱心,謝謝你/images/emoticon/emoticon41.gif

0
Steven C.
iT邦新手 5 級 ‧ 2020-09-24 14:26:57

看到這邊好像有筆誤: reflaction => reflection

taiansu iT邦新手 4 級 ‧ 2020-09-24 15:10:45 檢舉

喔喔喔萬分感謝~

我要留言

立即登入留言