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

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

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

Nov 12, 2016 am 10:17 AM

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

functional-coding.jpg

先看代碼

這個(gè)代碼平淡無奇,就是從一個(gè)數(shù)組中找到一個(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語言,讓其看上去更像一個(gè)表達(dá)式,動(dòng)用了 ? 號表達(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ǔ)充一些知識。
Javascript的箭頭函數(shù)



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

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

下面是一些示例:

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]

看上去不復(fù)雜吧。不過,上面前兩個(gè) simple 和 max 的例子都把這箭頭函數(shù)賦值給了一個(gè)變量,于是它就有了一個(gè)名字。有時(shí)候,某些函數(shù)在聲明的時(shí)候就是調(diào)用的時(shí)候,尤其是函數(shù)式編程中,一個(gè)函數(shù)還對外返回函數(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ù),可以寫成:

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

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

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

我還是加上括號,和換行可能會更清楚一些:

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

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


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

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

一般來說,遞歸的代碼就是函數(shù)自己調(diào)用自己,比如我們求階乘的代碼:

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

在匿名函數(shù)下,這個(gè)遞歸該怎么寫呢?對于匿名函數(shù)來說,我們可以把匿名函數(shù)當(dāng)成一個(gè)參數(shù)傳給另外一個(gè)函數(shù),因?yàn)楹瘮?shù)的參數(shù)有名字,所以就可以調(diào)用自己了。 如下所示:

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

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

(func) => (func(func))


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

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

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

然后,我們再把上面這個(gè)版本變成箭頭函數(shù)的匿名函數(shù)版:var fact = (func, n) => ( n==0 ? 1 : n * func(func, n-1) )

fact(fact, 5)

這里,我們依然還要用一個(gè)fact來保存這個(gè)匿名函數(shù),我們繼續(xù),我們要讓匿名函數(shù)聲明的時(shí)候,就自己調(diào)用自己。

也就是說,我們要把

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

這個(gè)函數(shù)當(dāng)成調(diào)用參數(shù),傳給下面這個(gè)函數(shù):

(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ù)
);

好像有點(diǎn)繞,anyway, 你看懂了嗎?沒事,我們繼續(xù)。
動(dòng)用高階函數(shù)的遞歸


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

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

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

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

連起來寫就是:

HighOrderFact ( HighOrderFact ) ( 5 )

但是,這樣讓用戶來調(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)一下,是不是簡潔了一些?

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

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

我們再來看那個(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)成了 ?號表達(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è)比原來版的那個(gè)簡單了很多,原來版本的那個(gè)又在函數(shù)中套了一套 next, 而且還動(dòng)用了不定參數(shù),當(dāng)然,如果你想裝逼裝到天上的,理論上來說,你可以套N層,呵呵。

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

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

本站聲明
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請聯(lián)系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脫衣機(jī)

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)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級代碼編輯軟件(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72