對於函數式的版本,乍一看,的確令人非常費解,仔細看一下,你可能就暈掉了,似乎完全就是天書,看上去非常裝逼,哈哈。不過,我感覺解析那段函數式的代碼可能會一個比較有趣過程。
先看代碼
這個代碼平淡無奇,就是從一個數組中找到一個數,O(n)的算法,找不到就返回 null。
下面是正常的 old-school 的方式。不用多說。
結果到了函數式成了下面這個樣子(好像上面的那些代碼在下面若影若現,不過又有點不太一樣,爲了消掉if語言,讓其看上去更像一個表達式,動用了 ? 號表達式):
爲了講清這個代碼,需要先補充一些知識。
Javascript的箭頭函數
首先先簡單說明一下,ECMAScript2015 引入的箭頭表達式。箭頭函數其實都是匿名函數,其基本語法如下:
下面是一些示例:
看上去不復雜吧。不過,上面前兩個 simple 和 max 的例子都把這箭頭函數賦值給了一個變量,於是它就有了一個名字。有時候,某些函數在聲明的時候就是調用的時候,尤其是函數式編程中,一個函數還對外返回函數的時候。比如下在這個例子:
其實,在 MakePowerFn 函數裏的那個 PowerFn 根本不需要命名,完全可以寫成:
如果用箭頭函數,可以寫成:
我們還可以寫得更簡潔(如果用表達式的話,就不需要 { 和 }, 以及 return 語句 ):
我還是加上括號,和換行可能會更清楚一些:
好了,有了上面的知識,我們就可以進入一個更高級的話題——匿名函數的遞歸。
匿名函數的遞歸
函數式編程立志於用函數表達式消除有狀態的函數,以及for/while循環,所以,在函數式編程的世界裏是不應該用for/while循環的,而要改用遞歸(遞歸的性能很差,所以,一般是用尾遞歸來做優化,也就是把函數的計算的狀態當成參數一層一層的往下傳遞,這樣語言的編譯器或解釋器就不需要用函數棧來幫你保存函數的內部變量的狀態了)。
好了,那麼,匿名函數的遞歸該怎麼做?
一般來說,遞歸的代碼就是函數自己調用自己,比如我們求階乘的代碼:
在匿名函數下,這個遞歸該怎麼寫呢?對於匿名函數來說,我們可以把匿名函數當成一個參數傳給另外一個函數,因爲函數的參數有名字,所以就可以調用自己了。 如下所示:
這個是不是有點作弊的嫌疑?Anyway,我們再往下,把上面這個函數整成箭頭函數式的匿名函數的樣子。
現在你似乎就不像作弊了吧。把上面那個求階乘的函數套進來是這個樣子:
首先,先重構一下fact,把fact中自己調用自己的名字去掉:
然後,我們再把上面這個版本變成箭頭函數的匿名函數版:
這裏,我們依然還要用一個fact來保存這個匿名函數,我們繼續,我們要讓匿名函數聲明的時候,就自己調用自己。
也就是說,我們要把
這個函數當成調用參數,傳給下面這個函數:
最終我們得到下面的代碼:
好像有點繞,anyway, 你看懂了嗎?沒事,我們繼續。
動用高階函數的遞歸
但是上面這個遞歸的匿名函數在自己調用自己,所以,代碼中有hard code的實參。我們想實參去掉,如何去掉呢?我們可以參考前面說過的那個
我們可以看,上面的代碼簡單說來就是,需要一個函數做參數,然後返回這個函數的遞歸版本。那麼,我們怎麼調用呢?
連起來寫就是:
但是,這樣讓用戶來調用很不爽,所以,以我們一個函數把 HighOrderFact ( HighOrderFact ) 給代理一下:
用箭頭函數重構一下,是不是簡潔了一些?
上面就是我們最終版的階乘的函數式代碼。
回顧之前的程序
我們再來看那個查找數組的正常程序:
先把for幹掉,搞成遞歸版本:
然後,寫出帶實參的匿名函數的版本(注:其中的if代碼被重構成了 ?號表達式):
最後,引入高階函數,去除實參:
注:函數式編程裝逼時一定要用const字符,這表示我寫的函數裏的狀態是 immutable 的,天生驕傲!
再注:我寫的這個比原來版的那個簡單了很多,原來版本的那個又在函數中套了一套 next, 而且還動用了不定參數,當然,如果你想裝逼裝到天上的,理論上來說,你可以套N層,呵呵。
現在,你可以體會到,如此逼裝的是怎麼來的了吧?
在學習中有不懂迷茫的童鞋們可以加我一起交流學習(聯繫方式看主頁個籤),無論你是大牛還是小白,是想轉行還是想入行都可以來了解一起進步一起學習!