亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

首頁(yè) php教程 PHP源碼 如何讀懂並寫出裝逼的函數(shù)式程式碼

如何讀懂並寫出裝逼的函數(shù)式程式碼

Nov 12, 2016 am 10:17 AM

今天在微博上看到了有人分享了下面的這段函數(shù)式程式碼,我把程式碼貼到下面,不過我對(duì)原來(lái)的程式碼略有改動(dòng),對(duì)於函數(shù)式的版本,咋一看,的確令人非常費(fèi)解,仔細(xì)看一下,你可能就暈掉了,似乎完全就是天書,看起來(lái)非常裝逼,哈哈。不過,我感覺解析那段函數(shù)式的程式碼可能會(huì)一個(gè)比較有趣過程,而且,我以前寫過一篇《函數(shù)式程式設(shè)計(jì)》的入門式的文章,正好可以用這個(gè)例子,再昇華一下原來(lái)的那篇文章,順便向大家更好的介紹很多基礎(chǔ)知識(shí),所以寫下這篇文章。

如何讀懂並寫出裝逼的函數(shù)式程式碼

先看程式碼

這個(gè)程式碼平淡無(wú)奇,就是從一個(gè)陣列中找到一個(gè)數(shù),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))

結(jié)果到了函數(shù)式成了下面這個(gè)樣子(好像上面的那些程式碼在下面若影若現(xiàn),不過又有點(diǎn)不太一樣,為了消掉if語(yǔ)言,讓其看上去更像一個(gè)表達(dá)式,動(dòng)用了? 號(hào)表達(dá)式):

//函數(shù)式的版本
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))

為了講清這個(gè)程式碼,需要先補(bǔ)充一些知識(shí)。
Javascript的箭頭函數(shù)



首先先簡(jiǎn)單說明一下,ECMAScript2015 引入的箭頭表達(dá)式。箭頭函數(shù)其實(shí)都是匿名函數(shù),其基本語(yǔ)法如下:

(param1, param2, …, paramN) => { statements } 
(param1, param2, …, paramN) => expression
     // 等于 :  => { return expression; } 
// 只有一個(gè)參數(shù)時(shí),括號(hào)才可以不加:
(singleParam) => { statements }
singleParam => { statements }
//如果沒有參數(shù),就一定要加括號(hào):
() => { statements }

下面是一些範(fàn)例:

var simple = a => a > 15 ? 15 : a; 
simple(16); // 15
simple(10); // 10
let 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]

看起來(lái)不複雜吧。不過,上面前兩個(gè) simple 和 max 的例子都把這箭頭函數(shù)賦值給了一個(gè)變量,於是它就有了一個(gè)名字。有時(shí)候,某些函數(shù)在宣告的時(shí)候就是在呼叫的時(shí)候,尤其是在函數(shù)式程式設(shè)計(jì)中,一個(gè)函數(shù)還對(duì)外傳回函數(shù)的時(shí)候。例如下在這個(gè)例子:

function MakePowerFn(power) {
  return function PowerFn(base) {
    return Math.pow(base, power);
  } 
}
power3 = MakePowerFn(3); //制造一個(gè)X的3次方的函數(shù)
power2 = MakePowerFn(2); //制造一個(gè)X的2次方的函數(shù)
console.log(power3(10)); //10的3次方 = 1000
console.log(power2(10)); //10的2次方 = 100

其實(shí),在MakePowerFn 函數(shù)裡的那個(gè)PowerFn 根本不需要命名,完全可以寫成:

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

如果用箭頭函數(shù),可以寫成:

rereee

reee更簡(jiǎn)潔(如果用表達(dá)式的話,就不需要{ 和}, 以及return 語(yǔ)句):

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

我還是加上括號(hào),和換行可能會(huì)更清楚:

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

好了,有了上面的知識(shí),我們就可以進(jìn)入一個(gè)更高級(jí)的話題——匿名函數(shù)的遞歸。
匿名函數(shù)的遞歸


函數(shù)式程式設(shè)計(jì)立志於用函數(shù)表達(dá)式消除有狀態(tài)的函數(shù),以及for/while循環(huán),所以,在函數(shù)式程式設(shè)計(jì)的世界裡是不應(yīng)該用for/while循環(huán)的,而要改用遞歸(遞歸的效能很差,所以,一般是用尾遞歸來(lái)做優(yōu)化,也就是把函數(shù)的計(jì)算的狀態(tài)當(dāng)成參數(shù)一層一層的往下傳遞,這樣語(yǔ)言的編譯器或解釋器就不需要用函數(shù)堆疊來(lái)幫你保存函數(shù)的內(nèi)部變數(shù)的狀態(tài)了)。

好了,那麼,匿名函數(shù)的遞歸該怎麼做?

一般來(lái)說,遞歸的程式碼就是函數(shù)自己呼叫自己,例如我們求階乘的程式碼:

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

在匿名函數(shù)下,這個(gè)遞歸該怎麼寫呢?對(duì)匿名函數(shù)來(lái)說,我們可以把匿名函數(shù)當(dāng)成一個(gè)參數(shù)傳給另一個(gè)函數(shù),因?yàn)楹瘮?shù)的參數(shù)有名字,所以就可以呼叫自己了。 如下:

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

這個(gè)是不是有點(diǎn)作弊的嫌疑? Anyway,我們?cè)偻?,把上面這個(gè)函數(shù)整成箭頭函數(shù)式的匿名函數(shù)的樣子。

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


現(xiàn)在你似乎就不像作弊了吧。把上面那個(gè)求階乘的函數(shù)套進(jìn)來(lái)是這個(gè)樣子:

首先,先重構(gòu)一下fact,把fact中自己調(diào)用自己的名字去掉:

(func) => (func(func))

然後,我們?cè)侔焉厦孢@個(gè)版本變成箭頭函數(shù)的匿名函數(shù)版:var?fact?=?(func,?n)?=>?(?n==0???1?:??n?*?func(func,?n-1)?)

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

在這裡,我們?nèi)钥墒褂眠@個(gè)一個(gè)一個(gè)匿名來(lái)保存這個(gè)一個(gè)函數(shù),我們繼續(xù),我們要讓匿名函數(shù)宣告的時(shí)候,就自己呼叫自己。

也就是說,我們要把

fact(fact, 5)

這個(gè)函數(shù)當(dāng)成呼叫參數(shù),傳給下面這個(gè)函數(shù):

(func, n) => ( n==0 ? 1 :  n * func(func, n-1) )


最後我們得到下面的程式碼:

(func, x) => func(func, x)


最後我們得到下面的程式碼:

( (func, x) => func(func, x) ) (  //函數(shù)體
  (func, n) => ( n==0 ? 1 :  n * func(func, n-1) ), //第一個(gè)調(diào)用參數(shù)
  5 //第二調(diào)用參數(shù)
);

最後我們得到下面的程式碼:

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


最後我們得到下面的程式碼:

fact = HighOrderFact(HighOrderFact);
fact(5);
看懂了嗎?沒事,我們繼續(xù)。

動(dòng)用高階函數(shù)的遞歸

????????但是上面這個(gè)遞歸的匿名函數(shù)在自己調(diào)用自己,所以,程式碼中有hard code的實(shí)參。我們想實(shí)參去掉,如何去掉呢?我們可以參考前面說的那個(gè) MakePowerFn 的例子,但這回是遞歸版的高階函數(shù)了。 ??????
HighOrderFact ( HighOrderFact ) ( 5 )
??我們可以看,上面的程式碼簡(jiǎn)單說來(lái)就是,需要一個(gè)函數(shù)做參數(shù),然後回傳這個(gè)函數(shù)的遞歸版本。那麼,我們要怎麼調(diào)用呢? ??????
fact = HighOrderFact(HighOrderFact);
fact(5);

連起來(lái)寫就是:

HighOrderFact ( HighOrderFact ) ( 5 )

但是,這樣讓用戶來(lái)調(diào)用很不爽,所以,以我們一個(gè)函數(shù)把 HighOrderFact ( HighOrderFact ) 給代理一下:

fact = function ( hifunc ) {
  return hifunc ( hifunc );
} (
  //調(diào)用參數(shù)是一個(gè)函數(shù)
  function (func) { 
    return function(n){
      return n==0 ? 1 : n * func(func)(n-1);
    };
  }
);
fact(5); //于是我們就可以直接使用了

用箭頭函數(shù)重構(gòu)一下,是不是簡(jiǎn)潔了一些?

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

上面就是我們最終版的階乘的函數(shù)式代碼。
回顧之前的程序

我們?cè)賮?lái)看那個(gè)查找數(shù)組的正常程序:

//正常的版本
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);
}

然后,寫出帶實(shí)參的匿名函數(shù)的版本(注:其中的if代碼被重構(gòu)成了 ?號(hào)表達(dá)式):

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

最后,引入高階函數(shù),去除實(shí)參:

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

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

再注:我寫的這個(gè)比原來(lái)版的那個(gè)簡(jiǎn)單了很多,原來(lái)版本的那個(gè)又在函數(shù)中套了一套 next, 而且還動(dòng)用了不定參數(shù),當(dāng)然,如果你想裝逼裝到天上的,理論上來(lái)說,你可以套N層,呵呵。

現(xiàn)在,你可以體會(huì)到,如此逼裝的是怎么來(lái)的了吧?。
其它

你還別說這就是裝逼,簡(jiǎn)單來(lái)說,我們可以使用數(shù)學(xué)的方式來(lái)完成對(duì)復(fù)雜問題的描述,那怕是遞歸。其實(shí),這并不是新鮮的東西,這是Alonzo Church 和 Haskell Curry 上世紀(jì)30年代提出來(lái)的東西,這個(gè)就是 Y Combinator 的玩法,關(guān)于這個(gè)東西,你可以看看下面兩篇文章:《The Y Combinator (Slight Return)》,《Wikipedia: Fixed-point combinator》

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願(yuàn)投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請(qǐng)聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動(dòng)的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁(yè)開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級(jí)程式碼編輯軟體(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72