<strike id="cy2gs"><menu id="cy2gs"></menu></strike>
  • <del id="cy2gs"><dfn id="cy2gs"></dfn></del>
  • 如何讀懂并寫出裝逼的函數(shù)式代碼

    2016-10-25    藍藍設(shè)計的小編

    如果您想訂閱本博客內(nèi)容,每天自動發(fā)到您的郵箱中, 請點這里

     

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


    先看代碼

    這個代碼平淡無奇,就是從一個數(shù)組中找到一個數(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ù)式成了下面這個樣子(好像上面的那些代碼在下面若影若現(xiàn),不過又有點不太一樣,為了消掉if語言,讓其看上去更像一個表達式,動用了 ? 號表達式):

    //函數(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))

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

    Javascript的箭頭函數(shù)

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

    (param1, param2, …, paramN) => { statements } 
    (param1, param2, …, paramN) => expression // 等于 :  => { return expression; }  // 只有一個參數(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ù)雜吧。不過,上面前兩個 simple 和 max 的例子都把這箭頭函數(shù)賦值給了一個變量,于是它就有了一個名字。有時候,某些函數(shù)在聲明的時候就是調(diào)用的時候,尤其是函數(shù)式編程中,一個函數(shù)還對外返回函數(shù)的時候。比如下在這個例子:

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

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

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

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

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

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

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

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

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

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

    匿名函數(shù)的遞歸

    函數(shù)式編程立志于用函數(shù)表達式消除有狀態(tài)的函數(shù),以及for/while循環(huán),所以,在函數(shù)式編程的世界里是不應(yīng)該用for/while循環(huán)的,而要改用遞歸(遞歸的性能很差,所以,一般是用尾遞歸來做優(yōu)化,也就是把函數(shù)的計算的狀態(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ù)下,這個遞歸該怎么寫呢?對于匿名函數(shù)來說,我們可以把匿名函數(shù)當(dāng)成一個參數(shù)傳給另外一個函數(shù),因為函數(shù)的參數(shù)有名字,所以就可以調(diào)用自己了。 如下所示:

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

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

    func) => (func(func))

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

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

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

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

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

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

    也就是說,我們要把

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

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

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

    最終我們得到下面的代碼:

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

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

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

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

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

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

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

    連起來寫就是:

    HighOrderFact ( HighOrderFact ) ( 5 )

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

    fact = function ( hifunc ) { return hifunc ( hifunc );
    } ( //調(diào)用參數(shù)是一個函數(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ù)式代碼。

    回顧之前的程序

    我們再來看那個查找數(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ù)的版本(注:其中的if代碼被重構(gòu)成了 ?號表達式):

    ( (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) ), //第一個調(diào)用參數(shù) arr, //第二調(diào)用參數(shù) 2 //第三調(diào)用參數(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ù)式編程裝逼時一定要用const字符,這表示我寫的函數(shù)里的狀態(tài)是 immutable 的,天生驕傲!

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

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

    其它

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

     

    藍藍設(shè)計www.skdbbs.com )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計BS界面設(shè)計  cs界面設(shè)計  ipad界面設(shè)計  包裝設(shè)計  圖標(biāo)定制  用戶體驗 、交互設(shè)計、 網(wǎng)站建設(shè) 平面設(shè)計服務(wù) 

     

    日歷

    鏈接

    個人資料

    藍藍設(shè)計的小編 http://www.skdbbs.com

    存檔

    主站蜘蛛池模板: 国产99久久精品一区二区| 欧美激情精品久久久久| 55夜色66夜色国产精品视频| 国产福利精品视频自拍 | 一本一道久久精品综合| 日韩精品专区AV无码| 久久精品无码一区二区app| 麻豆精品久久精品色综合| 日韩AV无码精品人妻系列| 久久精品国产第一区二区| 秋霞午夜鲁丝片午夜精品久| 国产精品视频白浆免费视频| 亚洲永久精品ww47| 亚洲国产成人a精品不卡在线| 国产精品高清在线| 老司机91精品网站在线观看| 第一福利永久视频精品| 国内少妇偷人精品视频免费 | 久久精品国产亚洲欧美| 爽爽精品dvd蜜桃成熟时电影院| 欧美日韩精品系列一区二区三区| 丰满人妻熟妇乱又伦精品劲 | 国产精品99久久久久久人| 精品欧洲AV无码一区二区男男 | 狠狠色丁香婷婷综合精品视频| 少妇精品久久久一区二区三区| 亚洲午夜精品第一区二区8050| 日本欧美国产精品第一页久久| 久久精品无码一区二区三区日韩| 国产亚洲精品a在线观看| 国产精品.XX视频.XXTV| 欧美国产日本精品一区二区三区| 99在线精品视频观看免费| 精品人妻中文字幕有码在线| 亚洲av永久无码精品网站| 亚洲精品无码高潮喷水在线| 伊人 久久 精品| 亚洲欧美精品SUV| 在线精品亚洲一区二区小说| 亚洲无码精品浪潮| 日产欧美国产日韩精品|