搜尋
首頁php教程PHP源码如何讀懂並寫出裝逼的函數式程式碼

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

如何讀懂並寫出裝逼的函數式程式碼

先看程式碼

這個程式碼平淡無奇,就是從一個陣列中找到一個數,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); // 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]

看起來不複雜吧。不過,上面前兩個 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);
  } 
}

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

rereee

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

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

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

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

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


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

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

一般來說,遞歸的程式碼就是函數自己呼叫自己,例如我們求階乘的程式碼:

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

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

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

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

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


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

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

(func) => (func(func))

然後,我們再把上面這個版本變成箭頭函數的匿名函數版: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

在這裡,我們仍可使用這個一個一個匿名來保存這個一個函數,我們繼續,我們要讓匿名函數宣告的時候,就自己呼叫自己。

也就是說,我們要把

fact(fact, 5)

這個函數當成呼叫參數,傳給下面這個函數:

(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 //第二调用参数
);

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

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


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

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

動用高階函數的遞歸

🎜🎜🎜🎜但是上面這個遞歸的匿名函數在自己調用自己,所以,程式碼中有hard code的實參。我們想實參去掉,如何去掉呢?我們可以參考前面說的那個 MakePowerFn 的例子,但這回是遞歸版的高階函數了。 🎜🎜🎜
HighOrderFact ( HighOrderFact ) ( 5 )
🎜我們可以看,上面的程式碼簡單說來就是,需要一個函數做參數,然後回傳這個函數的遞歸版本。那麼,我們要怎麼調用呢? 🎜🎜🎜
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》

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱工具

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器