如何讀懂并寫出裝逼的函數式代碼

今天在微博上看到了 有人分享了下面的這段函數式代碼,我把代碼貼到下面,不過我對原來的代碼略有改動,對于函數式的版本,咋一看,的確令人非常費解,仔細看一下,你可能就暈掉了,似乎完全就是天書,看上去非常裝逼,哈哈。不過,我感覺解析那段函數式的代碼可能會一個比較有趣過程,而且,我以前寫過一篇《函數式編程》的入門式的文章,正好可以用這個例子,再升華一下原來的那篇文章,順便可以向大家更好的介紹很多基礎知識,所以寫下這篇文章。

先看代碼

這個代碼平淡無奇,就是從一個數組中找到一個數,O(n)的算法,找不到就返回 null。

下面是正常的 old-school 的方式。不用多說。

//正常的版本
function find (x, y) {for ( let i = 0; i < x.length; i++ ) {if ( x[i] == y ) return i;}return null;
}let arr = [0,1,2,3,4,5]
console.log(find(arr, 2))
console.log(find(arr, 8))

結果到了函數式成了下面這個樣子(好像上面的那些代碼在下面若影若現,不過又有點不太一樣,為了消掉if語言,讓其看上去更像一個表達式,動用了 ? 號表達式):

//函數式的版本
const find = ( f => f(f) ) ( f =>(next => (x, y, i = 0) =>( i >= x.length) ?  null :( x[i] == y ) ? i :next(x, y, i+1))((...args) =>(f(f))(...args)))let arr = [0,1,2,3,4,5]
console.log(find(arr, 2))
console.log(find(arr, 8))

為了講清這個代碼,需要先補充一些知識。

Javascript的箭頭函數
首先先簡單說明一下,ECMAScript2015 引入的箭頭表達式。箭頭函數其實都是匿名函數,其基本語法如下:

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression

 // 等于 :  => { return expression; } 

// 只有一個參數時,括號才可以不加:
(singleParam) => { statements }
singleParam => { statements }

//如果沒有參數,就一定要加括號:
() => { statements }
下面是一些示例:

var simple = a => a > 15 ? 15 : a; 
simple(16); // 15
simple(10); // 10let max = (a, b) => a > b ? a : b;// Easy array filtering, mapping, ...var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b);  // 66
var even = arr.filter(v => v % 2 == 0); // [6, 0, 18]
var double = arr.map(v => v * 2);       // [10, 12, 26, 0, 2, 36, 46]

看上去不復雜吧。不過,上面前兩個 simple 和 max 的例子都把這箭頭函數賦值給了一個變量,于是它就有了一個名字。有時候,某些函數在聲明的時候就是調用的時候,尤其是函數式編程中,一個函數還對外返回函數的時候。比如下在這個例子:

function MakePowerFn(power) {return function PowerFn(base) {return Math.pow(base, power);} 
}

power3 = MakePowerFn(3); //制造一個X的3次方的函數
power2 = MakePowerFn(2); //制造一個X的2次方的函數

console.log(power3(10)); //10的3次方 = 1000
console.log(power2(10)); //10的2次方 = 100
其實,在 MakePowerFn 函數里的那個 PowerFn 根本不需要命名,完全可以寫成:

function MakePowerFn(power) {return function(base) {return Math.pow(base, power);} 
}

如果用箭頭函數,可以寫成:

MakePowerFn = power  => {return base => {return Math.pow(base, power);} 
}

我們還可以寫得更簡潔(如果用表達式的話,就不需要 { 和 }, 以及 return 語句 ):

MakePowerFn = power => base => Math.pow(base, power)
我還是加上括號,和換行可能會更清楚一些:

MakePowerFn = (power) => ((base) => (Math.pow(base, power))
)

好了,有了上面的知識,我們就可以進入一個更高級的話題——匿名函數的遞歸。

匿名函數的遞歸
函數式編程立志于用函數表達式消除有狀態的函數,以及for/while循環,所以,在函數式編程的世界里是不應該用for/while循環的,而要改用遞歸(遞歸的性能很差,所以,一般是用尾遞歸來做優化,也就是把函數的計算的狀態當成參數一層一層的往下傳遞,這樣語言的編譯器或解釋器就不需要用函數棧來幫你保存函數的內部變量的狀態了)。

好了,那么,匿名函數的遞歸該怎么做?

一般來說,遞歸的代碼就是函數自己調用自己,比如我們求階乘的代碼:

function fact(n){return n==0 ? 1 :  n * fact(n-1);
};
result = fact(5);

在匿名函數下,這個遞歸該怎么寫呢?對于匿名函數來說,我們可以把匿名函數當成一個參數傳給另外一個函數,因為函數的參數有名字,所以就可以調用自己了。 如下所示:

function combinator(func) {func(func);
}

這個是不是有點作弊的嫌疑?Anyway,我們再往下,把上面這個函數整成箭頭函數式的匿名函數的樣子。

(func) => (func(func))
現在你似乎就不像作弊了吧。把上面那個求階乘的函數套進來是這個樣子:

首先,先重構一下fact,把fact中自己調用自己的名字去掉:

function fact(func, n) {return n==0 ? 1 :  n * func(func, n-1);
}fact(fact, 5); //輸出120

然后,我們再把上面這個版本變成箭頭函數的匿名函數版:

var fact = (func, n) => ( n==0 ? 1 : n * func(func, n-1) )
fact(fact, 5)
這里,我們依然還要用一個fact來保存這個匿名函數,我們繼續,我們要讓匿名函數聲明的時候,就自己調用自己。

也就是說,我們要把

(func, n) => ( n==0 ? 1 : n * func(func, n-1) )
這個函數當成調用參數,傳給下面這個函數:

(func, x) => func(func, x)
最終我們得到下面的代碼:

( (func, x) => func(func, x) ) (  //函數體(func, n) => ( n==0 ? 1 :  n * func(func, n-1) ), //第一個調用參數5 //第二調用參數
);

好像有點繞,anyway, 你看懂了嗎?沒事,我們繼續。

動用高階函數的遞歸
但是上面這個遞歸的匿名函數在自己調用自己,所以,代碼中有hard code的實參。我們想實參去掉,如何去掉呢?我們可以參考前面說過的那個 MakePowerFn 的例子,不過這回是遞歸版的高階函數了。

HighOrderFact = function(func){return function(n){return n==0 ? 1 : n * func(func)(n-1);};
};

我們可以看,上面的代碼簡單說來就是,需要一個函數做參數,然后返回這個函數的遞歸版本。那么,我們怎么調用呢?

fact = HighOrderFact(HighOrderFact);
fact(5);

連起來寫就是:

HighOrderFact ( HighOrderFact ) ( 5 )

但是,這樣讓用戶來調用很不爽,所以,以我們一個函數把 HighOrderFact ( HighOrderFact ) 給代理一下:

fact = function ( hifunc ) {return hifunc ( hifunc );
} (//調用參數是一個函數function (func) { return function(n){return n==0 ? 1 : n * func(func)(n-1);};}
);

fact(5); //于是我們就可以直接使用了
用箭頭函數重構一下,是不是簡潔了一些?

fact = (highfunc => highfunc ( highfunc ) ) (func => n =>  n==0 ? 1 : n * func(func)(n-1)
);

上面就是我們最終版的階乘的函數式代碼。

回顧之前的程序
我們再來看那個查找數組的正常程序:

//正常的版本
function find (x, y) {for ( let i = 0; i < x.length; i++ ) {if ( x[i] == y ) return i;}return null;
}

先把for干掉,搞成遞歸版本:

function find (x, y, i=0) {if ( i >= x.length ) return null;if ( x[i] == y ) return i;return find(x, y, i+1);
}

然后,寫出帶實參的匿名函數的版本(注:其中的if代碼被重構成了 ?號表達式):

( (func, x, y, i) => func(func, x, y, i) ) (  //函數體(func, x, y, i=0) => (i >= x.length ?  null :x[i] == y  ?  i : func (func, x, y, i+1)), //第一個調用參數arr, //第二調用參數2 //第三調用參數
)

最后,引入高階函數,去除實參:

const find = ( highfunc => highfunc( highfunc ) ) (func => (x, y, i = 0) => (i >= x.length ?  null :x[i] == y  ?  i : func (func) (x, y, i+1))
);

注:函數式編程裝逼時一定要用const字符,這表示我寫的函數里的狀態是 immutable 的,天生驕傲!

再注:我寫的這個比原來版的那個簡單了很多,原來版本的那個又在函數中套了一套 next, 而且還動用了不定參數,當然,如果你想裝逼裝到天上的,理論上來說,你可以套N層,呵呵。

現在,你可以體會到,如此逼裝的是怎么來的了吧?。

其它
你還別說這就是裝逼,簡單來說,我們可以使用數學的方式來完成對復雜問題的描述,那怕是遞歸。其實,這并不是新鮮的東西,這是Alonzo Church 和 Haskell Curry 上世紀30年代提出來的東西,這個就是 Y Combinator 的玩法,關于這個東西,你可以看看下面兩篇文章:

《The Y Combinator (Slight Return)》,

《Wikipedia: Fixed-point combinator》

(全文完)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/277202.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/277202.shtml
英文地址,請注明出處:http://en.pswp.cn/news/277202.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何打通高薪的黃金通道 成為職場金領

身在職場的你&#xff0c;是否想過有朝一日能獲得百萬年薪&#xff1f;最近&#xff0c;央視二套絕對挑戰特別節目巔峰營銷的熱播&#xff0c;引發各方人士對東風日產百萬年薪招兵營銷總監的關注。身價百萬的營銷總監人人想當&#xff0c;如何才能成為這樣的職場金領&#xff0…

iView 實戰系列教程(21課時)_2.iView 實戰教程之導航、路由、鑒權篇

在c盤創建一個iview-router的項目、然后使用默認的配置跳過添加vue-router的插件編譯我們的文件。編譯好之后&#xff0c;我們啟動App默認的頁面就打開了。默認兩個路由一個是about界面一個是home我們使用編輯器打開代碼&#xff0c;用我們的iview的menu組件替換掉這兩個路由在…

計算機專業單元測試卷答案,銀保監會考試題庫:計算機類模擬試題練習(六)答案...

2020銀保監會招聘考試即將開始。根據往年經驗&#xff0c;銀保監會招聘考試科目包括行測、申論和專業科目共三科&#xff0c;專業科目是考試提分的關鍵&#xff0c;那么如何才能提升專業科目的做題速度并快速提分呢&#xff1f;別擔心&#xff0c;中公金融人小編根據歷年的考試…

Ubuntu安裝Nginx

在Ubuntu下安裝Nginx有以下方法&#xff0c;但是如果想要安裝最新版本的就必須下載源碼包編譯安裝。 一、基于APT源安裝 sudo apt-get install nginx 安裝好的文件位置&#xff1a; /usr/sbin/nginx&#xff1a;主程序 /etc/nginx&#xff1a;存放配置文件 /usr/share/nginx&am…

9.28PMP每日一題

控制質量過程的輸出是&#xff1a;A、變更請求B、批準的變更請求C、工作績效數據D、事業環境因素 答案將于明天和新題一起發布&#xff01;9.27試題答案&#xff1a;C轉載于:https://blog.51cto.com/13554215/2287292

將數組綁定到dropdownlist上

<% Page Language"C#" %><% Import Namespace"System.Data" %><Script Language"C#" Runat"Server">public void Page_Load(Object src,EventArgs e){ //首先建立一個數組 ArrayList arrnew ArrayList(); …

HTML標題h,HTML H標題標簽

可以將HTML標題或HTML h標簽定義為要在網頁上顯示的標題或副標題。當你將文本放在標題標簽……… h1>內時, 它在瀏覽器中以粗體顯示, 并且文本的大小取決于標題的數量。從到標簽定義了六個不同的HTML標題, 從最高級別h1(主標題)到最低級別h6(最重要的標題)。h1是最大的標題標…

湯姆大叔的6道javascript編程題題解

1、找出數字數組中最大的元素&#xff08;使用Math.max函數&#xff09; 123var a [1, 2, 3, 6, 5, 4];var ans Math.max.apply(null, a);console.log(ans); // 6這題很巧妙地用了apply&#xff0c;如果不是數組&#xff0c;是很多數字求最大值&#xff0c;我們知道可以這樣…

Android 多線程之幾個基本問題

Android中的進程和線程 Android中的一個應用程序一般就對應著一個進程&#xff0c;多進程的情況可以參考Android 多進程通信之幾個基本問題 Android中更常見的是多線程的情況&#xff0c;一個應用程序中一般都有包括UI線程等多個線程。Android中規定網絡訪問必須在子線程中進行…

Web下的整體測試

隨著Internet的日益普及&#xff0c;現在基于B/S結構的大型應用越來越多&#xff0c;可如何對這些應用進行測試成為日益迫切的問題。有許多測試人員來信問我B/S的測試如何做&#xff0c;由于工作較繁忙&#xff0c;對大家提出的問題也是頭痛醫頭腳痛醫腳&#xff0c;沒有對WEB的…

用計算機算算術平方根順序是ON然后是什么,第2課時用計算器求一個正數的算術平方根.ppt...

1.比較下列各組數的大小&#xff1a;(1) 與(2) 與 8依次按鍵顯示&#xff1a;1.732 050 808例3 小麗想用一塊面積為400 cm2的正方形紙片&#xff0c;沿著邊的方向剪出一塊面積為300 cm2的長方形紙片&#xff0c;使它的長寬之比為3:2&#xff0e;她不知能否裁得出來&#xff0c;…

MySQL 命令

版權聲明&#xff1a;本文首發 http://asing1elife.com &#xff0c;轉載請注明出處。 https://blog.csdn.net/asing1elife/article/details/82892834 MySQL 一些常見命令 更多精彩 更多技術博客&#xff0c;請移步 asing1elife’s blog 查看版本號 mysql -V重啟/啟動/停止 mys…

Bookshelf 2 簡單DFS

鏈接&#xff1a;https://ac.nowcoder.com/acm/contest/993/C來源&#xff1a;牛客網 題目描述 Farmer John recently bought another bookshelf for the cow library, but the shelf is getting filled up quite quickly, and now the only available space is at the top.FJ…

一步一步SharePoint 2007之五:向網站中添加一個子網站

一步一步SharePoint 2007之五&#xff1a;向網站中添加一個子網站摘要感受完看到成果的激動&#xff0c;感受完鄰家女孩的漂亮、可愛和端莊&#xff0c;不要停止&#xff0c;來&#xff0c;讓我們一起來動手打造心目中的完美女神吧&#xff01;本篇文章將介紹如何向一個網站中添…

微型計算機系統分為哪幾個層次,計算機系統分為哪4層?

滿意答案al053192014.06.23采納率&#xff1a;49% 等級&#xff1a;12已幫助&#xff1a;7516人第一層&#xff1a;物理層(PhysicalLayer)&#xff0c;規定通信設備的機械的、電氣的、功能的和過程的特性&#xff0c;用以建立、維護和拆除物理鏈路連接。具體地講&#xff0c…

ASP.NET Core 基礎教程 - ASP.NET Core 基礎教程 - 簡單教程,簡單編程

原文:ASP.NET Core 基礎教程 - ASP.NET Core 基礎教程 - 簡單教程&#xff0c;簡單編程 ASP.NET Core 是對 ASP.NET 有重大意義的一次重新設計。本章節我們將介紹 ASP.NET Core 中的一些新的概念和它們是如何幫助我們開發現代化的 Web 應用程序 盡管 ASP.NET Core 是跨平臺的&a…

參數初始化

通過以下形式進行參數初始化 self.fc nn.Linear(n_head * d_v, d_model) nn.init.xavier_normal_(self.fc.weight) 轉載于:https://www.cnblogs.com/yeran/p/11197047.html

使用input type=file 上傳文件時需注意

在asp.net的中使用<input typefile />控件上傳文件對文件的大小有限制,默認情況下大概在4m左右,如果上傳再大的文件時就會出頁面無法顯示的錯誤.修改web.config文件中的參數可以設置該控件上傳文件的大小,web.config中配置如下:在<system.web>節點下增加"<…

html模板 循環里if,django模板里循環變量table里想要兩個一行如何控制

2016-8-3 周三做項目時遇到的問題&#xff1a;每個div由循環變量輸出&#xff1a;{% for key,value in formextenddetail %}{{ key }}{{ value }}{% endfor %}但是我想兩個div一行&#xff0c;使用...這種樣子因為我負責的是前端&#xff0c;views這些不是很熟悉&#xff0c;想…

ASP.NET Core Windows 環境配置 - ASP.NET Core 基礎教程 - 簡單教程,簡單編程

原文:ASP.NET Core Windows 環境配置 - ASP.NET Core 基礎教程 - 簡單教程&#xff0c;簡單編程 ASP.NET Core Windows 環境配置 ASP.NET Core 是對 ASP.NET 有重大意義的一次重新設計。本章節我們將介紹 ASP.NET Core 中的一些新的概念和它們是如何幫助我們開發現代化的 Web 應…