<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          coding優(yōu)雅指南:函數(shù)式編程

          共 19098字,需瀏覽 39分鐘

           ·

          2021-06-21 23:29


          函數(shù)式編程是一種編程范式,主要思想是把程序用一系列計算的形式來表達(dá),主要關(guān)心數(shù)據(jù)到數(shù)據(jù)之間的映射關(guān)系。同時在react hook中,其實存在了大量的函數(shù)式編程的思想。所以作為一個前端,對于函數(shù)式編程的能力還是必須要有的。

          what is it?

          從兩種范式的區(qū)別講起

          • 命令式編程
            • 命令式編程是面向計算機硬件的抽象,變量對應(yīng)存儲單元,賦值對應(yīng)寄存器的存取指令,表達(dá)式對應(yīng)內(nèi)存引用和算術(shù)運算,控制語句對應(yīng)跳轉(zhuǎn)語句。命令式編程就是將程序用一系列的命令組織起來,將問題的步驟分解成一個個的命令的一種組織方式。
          • 函數(shù)式編程
            • 函數(shù)式編程是面向數(shù)學(xué)的一種抽象,關(guān)心的是數(shù)據(jù)到數(shù)據(jù)的映射過程,即是將計算過程抽象描述成一種表達(dá)式求值。
          • 在函數(shù)式語言中,函數(shù)作為一等公民,可以在任何地方定義,可以作為參數(shù)和返回值,可以對函數(shù)進(jìn)行組合。
          • 函數(shù)式編程中的函數(shù)不是指的計算機中的函數(shù)這個概念,而是數(shù)學(xué)界函數(shù)的概念,在初高中數(shù)學(xué)中,我們都學(xué)到了什么叫函數(shù),函數(shù)是一種從x -> y的一種映射關(guān)系,如果 f 這個映射規(guī)則定了,那么f(x) 的值僅與傳入的x有關(guān),函數(shù)式編程的思想其實就是如此,其執(zhí)行結(jié)果僅與輸入的參數(shù)有關(guān)不依賴其他外部的狀態(tài),也不會產(chǎn)生副作用,這種函數(shù)我們稱為純函數(shù)(pure Function)
          • 函數(shù)式編程中的變量也和命令式編程中的變量的概念不一致,命令式中的變量大多是指存儲單元的狀態(tài),而函數(shù)式中的變量值的是數(shù)學(xué)中代數(shù)上的變量,即一個值的名稱,變量的值是不可變的(immutable),即不可以多次給一個變量賦值。函數(shù)式編程從理論上說,是通過 lambda 演算來進(jìn)行的。

          pure function(純函數(shù)) & 副作用

          • 純函數(shù)是這樣一種函數(shù),只受輸入影響,與外部狀態(tài)無關(guān),即相同的輸入,永遠(yuǎn)會得到相同的輸出,而且沒有任何可觀察的副作用。
            • 此函數(shù)在相同的輸入值時,需產(chǎn)生相同的輸出。函數(shù)的輸出和輸入值以外的其他隱藏信息或狀態(tài)[2]無關(guān),也和由I/O設(shè)備產(chǎn)生的外部輸出無關(guān)。
            • 維基[1]上若一個函數(shù)符合以下要求,則它可能被認(rèn)為是純函數(shù)
          • 該函數(shù)不能有語義上可觀察的函數(shù)副作用[3],定義:副作用_是在計算結(jié)果的過程中,系統(tǒng)狀態(tài)的一種變化,或者與外部世界進(jìn)行的_可觀察的交互。副作用包括:諸如“觸發(fā)事件”,使輸出設(shè)備輸出,或更改輸出值以外物件的內(nèi)容,更詳細(xì)的栗子:更改文件系統(tǒng)、往數(shù)據(jù)庫插入記錄、發(fā)送http請求、可變數(shù)據(jù)、打印、獲取輸入、dom查詢、訪問系統(tǒng)狀態(tài)。

          純函數(shù)的輸出可以不用和所有的輸入值有關(guān),甚至可以和所有的輸入值都無關(guān)。但純函數(shù)的輸出不能和輸入值以外的任何資訊有關(guān)。純函數(shù)可以傳回多個輸出值,但上述的原則需針對所有輸出值都要成立。以下一個初中數(shù)學(xué)的圖,可以很好的說明這個道理。

          再往下(從lambda演算開始):

          計算機科學(xué),尤其是編程語言,經(jīng)常傾向于使用一種特定的演算:lambda演算[4] (Lambda Calculus)。這種演算也廣泛地被邏輯學(xué)家用于學(xué)習(xí)計算和離散數(shù)學(xué)的結(jié)構(gòu)的本質(zhì)。Lambda演算偉大的的原因有很多,其中包括:

          • 非常簡單。
          • 圖靈完備[5]
          • 容易讀寫。
          • 語義足夠強大,可以從它開始做(任意)推理。
          • 它有一個很好的實體模型。
          • 容易創(chuàng)建變種,以便我們探索各種構(gòu)建計算或語義方式的屬性。

          lambda演算最重要的兩個規(guī)約:Alpha規(guī)約和Beta規(guī)約,簡單來講,就是

          • 參數(shù)可以任意命名,參數(shù)名改變不影響lambda表達(dá)式
          • 參數(shù)標(biāo)識符可以用參數(shù)值代替

          初中數(shù)學(xué)知識,對吧。好的,我們再來引入一點知識:丘奇數(shù)[6], 這樣我們能表示一定的計算了,再引入邏輯定義,和循環(huán)定義,這樣是不是就能表達(dá)所有計算了。這里就不展開再繼續(xù)了,有興趣可以移步閱讀 https://cgnail.github.io/academic/lambda-1/[7]。函數(shù)式編程其實就是基于lambda演算衍生出來的。

          why use it ?

          從一個簡單的例子講起:

          const add = (x,y)=>x+y; 
          const multiply = (x,y)=>x*y; add(multiply(b, add(a, c)), multiply(a, b));
          // add(b*(a+c),a*b); // add(add(ab+ac),a*b); // ab+ab+ac multiply(a,add(b,add(b+c)))  

          從上面代碼可以看出來,函數(shù)式的優(yōu)點,可以任意組合,拆分。

          特點:

          • 輸出僅與輸入有關(guān)。
          • 引用透明不依賴外部,舉個栗子,就是外面不管地震海嘯刮風(fēng)下雨,你的媽媽在拿到番茄和雞蛋這兩個輸入以后,還是會輸出番茄炒蛋這個菜,不管外面發(fā)生什么,你給你的媽媽輸入番茄和雞蛋,總會得到番茄炒蛋這個菜。換到代碼中來就是函數(shù)式編程中的函數(shù)是沒有上下文的,無論上下文怎么變,這個函數(shù)的調(diào)用結(jié)果僅依賴于輸入的參數(shù)。
          • 不產(chǎn)生副作用。一般來講 操作數(shù)據(jù)庫 發(fā)起請求 操作dom 調(diào)用其他副作用函數(shù),這些活動一般會對外部環(huán)境產(chǎn)生影響。

          優(yōu)點:

          • 輸入輸出顯示,方便溯源,同時不會有隱式的狀態(tài)引入,導(dǎo)致該模塊在A處工作正常,但是在B處工作不正常
          // not pure
            const a = {x:5,y:10};
            const b = ()=>{
              console.log(a.x)
            }
            b();// => 5
             // later ...
            a.x =10;
            // later ...
            b(); // =>10
          // some thing are wrong  
          // 誒 輸出為什么變了, b里面怎么計算的,b依賴的啥呀
          // 誒 爺找到他怎么計算的了
          // 為啥前后不一致呢
          // 臥槽 這里為啥改了全局變量
          // pure
          const b = (obj)=>{
            console.log(obj?.a)
          }
          b({a:5});
          // 這個東西出問題了.. 參數(shù)被改變了 over over
          • 輸入輸出流顯式,只有一個渠道也就是輸入?yún)?shù)可以獲得數(shù)據(jù)。
          • 可以得到函數(shù)映射表、并發(fā)安全 避免競爭、無狀態(tài),不會讀取外部狀態(tài)。
          • 不產(chǎn)生副作用純函數(shù),可以組裝起來變成高級純函數(shù)可讀性高,可測試性,可復(fù)制和重構(gòu)

          展開講講:

          1. 輸入輸出顯示,那么我們可以得到這個函數(shù)的映射表,說明我們可以對這個函數(shù)計算結(jié)果進(jìn)行緩存,如果有同樣輸入的調(diào)用,那么我們可以直接返回計算后的值。
          const memo = (fn)=>{
              const cache = new Map();
              return (...args)=>{
                  const key = JSON.stringify(args);
                  if(cache.has(key)){
                      return cache.get(key);
                  }
                  const res = fn.call(fn,...args);
                  cache.set(key,res);
                  return res;
              }
          }
          const addOne = memo(x=>x+1);
          addOne(5); // 計算
          addOne(5); // 緩存
          1. 可以將一個不純的函數(shù)轉(zhuǎn)換成一個純函數(shù)
          const pureHttpGet = memo((url,params)=>{
            return ()=>{
              return axios.get(url,params);
            }
          })

          這個函數(shù)之所以能稱為純函數(shù),他滿足純函數(shù)的特性,根據(jù)輸入,總是返回固定的輸出。

          1. 可移植性 一句名言:“面向?qū)ο笳Z言的問題是,它們永遠(yuǎn)都要隨身攜帶那些隱式的環(huán)境。你只需要一個香蕉,但卻得到一個拿著香蕉的大猩猩...以及整個叢林”
          2. 可測試性與引用透明,對于一個純函數(shù),我們可以很清晰的去斷言他的輸入輸出,同時因為引用透明,可以很簡單的去推導(dǎo)出函數(shù)的內(nèi)部的調(diào)用過程,從而去簡化&重構(gòu)這個函數(shù)。
          3. 并行:回憶一下操作系統(tǒng)死鎖的原因,以及為什么有鎖這個機制的存在,就是因為需要使用/更改外部的資源,但是純函數(shù)不需要訪問共享內(nèi)存,所以也不會因為副作用進(jìn)入競爭態(tài)。

          core:

          1. 高階函數(shù)
          • 高階函數(shù)是指對一個函數(shù)可以傳入一個參數(shù)是函數(shù),或者返回值是函數(shù)。javascript是天生支持高階函數(shù)和閉包兩個重要特性的。我們常用的array方法中 map reduce filter .. 就是高階函數(shù)
          Array.prototype.fakemap = function (callback, thisArg{
              if (!Array.isArray(this)) {
                throw new Error("Type Error");
              }
              if(typeof callback!=="function"){
                  throw new Error(callback.toString()+'is not a function')
              }
              let resArr = [];
              let cb = callback.bind(thisArg);
              for (let i = 0; i < this.length; i++) {
                resArr.push(cb(this[i], i, this));
              }
              return resArr;
          };
          // 高階函數(shù)當(dāng)然也可以組合使用 與純函數(shù)性質(zhì)一致
          [1,2,3,4].filter(item=>item>2).map(item=>item -1
          1. 偏函數(shù)應(yīng)用 partial function
          • 偏函數(shù)和柯里化是兩個很容易混淆的概念,偏函數(shù)是包裝一個原始函數(shù)接受部分參數(shù)作為固定值預(yù)設(shè),返回一個新的函數(shù)。
          // 創(chuàng)建偏函數(shù),固定一些參數(shù)
          const partial = (f, ...args) =>
            // 返回一個帶有剩余參數(shù)的函數(shù)
            (...moreArgs) =>
              // 調(diào)用原始函數(shù)
              f(...args, ...moreArgs)

          const add3 = (a, b, c) => a + b + c

          // (...args) => add3(2, 3, ...args)
          // (c) => 2 + 3 + c
          const fivePlus = partial(add3, 23)

          fivePlus(4)  // 9

          js中最常見的 Function.prototype.bind(會改變this指向),其實就可以實現(xiàn)

          const foo = (a:number,b:number)=>{
            return a+b;
          }
          const bar = foo.bind(null,2);
          bar(3);//5

          簡單來說,偏函數(shù)就是固定部分參數(shù),返回新函數(shù)做計算,如果需要完整實現(xiàn)的話,可以參考一下lodash的partial.js這個文件,大致意思簡單的將源碼思路寫一下

          function partial(fn{
            const args = Array.slice.call(arguments,1);
            return function(){
              const length = args.length;
              let position = 0;
              // 把用占位符的參數(shù)替換掉
              for(let i =0 ;i<length,i++){
                args[i] = args[i]=== _ ? arguments[position++]:args[i]
              }
              // 將剩下的參數(shù)懟進(jìn)去
              while(position<arguments.length) args.push(arguments[postion++]);
              return fn.apply(this,args)
            };
          }
          1. 柯里化
          • 柯里化和偏函數(shù)應(yīng)用有些區(qū)別,是將多個參數(shù)函數(shù)轉(zhuǎn)換成單參數(shù)的函數(shù)。
          const curry = fn => {
            if (fn.length <= 1) {
              return fn;
            }
            const iter = args =>
              args.length === fn.length
                ? fn(...args)
                : arg => iter([...args, arg]);
            return iter([]);
          };
          1. 閉包

          閉包最初是來源于lambda演算中的一個概念,閉包(closure)或者叫完全綁定(complete binding)。在對一個Lambda演算表達(dá)式進(jìn)行求值的時候,不能引用任何未綁定的標(biāo)識符。如果一個標(biāo)識符是一個閉合Lambda表達(dá)式的參數(shù),我們則稱這個標(biāo)識符是(被)綁定的;如果一個標(biāo)識符在任何封閉上下文中都沒有綁定,那么它被稱為自由變量。

          • lambda x . plus x y:在這個表達(dá)式中,y和plus是自由的,因為他們不是任何閉合的Lambda表達(dá)式的參數(shù);而x是綁定的,因為它是函數(shù)定義的閉合表達(dá)式plus x y的參數(shù)。
          • lambda x y . y x:在這個表達(dá)式中x和y都是被綁定的,因為它們都是函數(shù)定義中的參數(shù)。
          • lambda y . (lambda x . plus x y):在內(nèi)層演算lambda x . plus x y中,y和plus是自由的,x是綁定的。在完整表達(dá)中,x和y是綁定的:x受內(nèi)層綁定,而y由剩下的演算綁定。plus仍然是自由的。

          一個Lambda演算表達(dá)式只有在其所有變量都是綁定的時候才完全合法。js中的閉包就不在這啰嗦了,為什么需要閉包,我們可以看一個最簡單的柯里化的例子。

          const add = (w,x,y,z)=>w+x+y+z;
          const curryAdd = curry(add);
          // 根據(jù)上面 寫的curry函數(shù), 我們來分解一下


          const a = curryAdd(1); // 這個時候返回了一個包含1的函數(shù) & add的函數(shù)
          const b = a(2);
          const c = b(3);
          const d = c(4);
          // 如果沒有閉包,之前傳入的東西參數(shù)都無跡可尋了。

          從webstorm單步調(diào)試可以更清楚的知道這個函數(shù)的作用域&存在哪些閉包。

          How do I use it ?

          • 去掉無必要的包裹
          // Bad
          const BlogController = {
            index(posts) { return Views.index(posts); },
            show(post) { return Views.show(post); },
            create(attrs) { return Db.create(attrs); },
            update(post, attrs) { return Db.update(post, attrs); },
            destroy(post) { return Db.destroy(post); },
          };
          // Good
          const BlogController = {
            index: Views.index,
            show: Views.show,
            create: Db.create,
            update: Db.update,
            destroy: Db.destroy,
          };

          BlogController,雖說添加一些沒有實際用處的間接層實現(xiàn)起來很容易,但這樣做除了徒增代碼量,提高維護和檢索代碼的成本外,沒有任何用處。

          另外,如果一個函數(shù)被不必要地包裹起來了,而且發(fā)生了改動,那么包裹它的那個函數(shù)也要做相應(yīng)的變更。

          foo(a, b => bar(b)); 

          如果 foo 增加回調(diào)中處理的函數(shù),那么不只是要改掉foo和bar,所有涉及到的調(diào)用也會更改。

          foo(a, (x, y) => bar(x, y)); 

          寫成一等公民函數(shù)的形式,要做的改動將會少得多:

          foo(a, bar);  // 只需要更改foo中執(zhí)行bar的邏輯和 bar中執(zhí)行的邏輯 
          • 工具函數(shù)命名更為通用(拒絕業(yè)務(wù)化)
          // 只針對當(dāng)前的博客
          const validArticles = articles =>
           articles.filter(article => article !== null && article !== undefined),

          // 對未來的項目更友好
          const compact = xs => xs.filter(x => x !== null && x !== undefined);
          • use the poor function

          使用javascript api過程中,不要使用含有副作用的API,而選擇無副作用的api。例如 slice 和 splice,肯定是選擇slice,不要修改傳入的引用對象等。來看一點case

          // bad errInfo 永久的被改動了,如果有其他地方使用到的話,可能會出現(xiàn)問題
          const valiateRepeatPhone = (phones: string[], errInfo: IErrorInfo[]) => {
            for (let i = 0; i < phones.length; i++) {
              if (errInfo[i] === ERROR_TYPE.PHONE_REPEAT) {
                errInfo[i] = ERROR_TYPE.NULL;
              }
              if (
                errInfo[i] === ERROR_TYPE.NULL &&
                phones[i] !== '' &&
                phones.indexOf(phones[i]) !== i
              ) {
                errInfo[i] = ERROR_TYPE.PHONE_REPEAT;
              }
            }
          };
          // 稍好一點的
          const valiateRepeatPhone = (phones: string[], errInfo: IErrorInfo[]) => {
             return errInfo.map((item,index)=>{
                 if(item === ERRORTYPE.PHONE_REPEAT || item === ERROR_TYPE.NULL){
                     return (phones[i] !== '' && phones.indexOf(phones[i]) !== i)
                             ? ERROR_TYPE.PHONE_REPEAT
                             : ERROR_TYPE.NULL
                 }
                 return item;
             })

          • 使用聲明式而不是命令式,將依賴當(dāng)作參數(shù)傳遞
          // 命令式
          const makes = [];
          for(let i =0;i<car.length;i++){
            makes.push(cars[i].make);
          }
          const makes = cars.map(item=>item.make)
          • compose(將一些純函數(shù)組合起來,返回新函數(shù)), 讓代碼從右到左運行,而不是由內(nèi)而外運行。
          const compose = (f,g) => {
            return x => {
              return f(g(x));
            }
          }
          • Monad :上面我們介紹了compose,但是compose的調(diào)用方式,總看起來還是沒有那么舒服,在js中鏈?zhǔn)秸{(diào)用很流行,要實現(xiàn)鏈?zhǔn)秸{(diào)用,例如(5).add(1).add(4),那么我們肯定需要一個容器,將5進(jìn)行一個包裝。
          class Functor {
              private val:any;
              private constructor (val: any) {
                 this.val = val;
              }
              public static of(val){
                  return new Functor(val);
              }
              public map(Fn){
                  return Functor.of(Fn(this.val))
              }
              
          }
          Functor.of(5).map(add5).map(double)

          好的,根據(jù)上面,其實我們已經(jīng)實現(xiàn)了一個函數(shù)式編程中比較重要的概念,函子(functor)。functor 是實現(xiàn)了 map 函數(shù)并遵守一些特定規(guī)則的容器類型。

          接下來我們進(jìn)一步分析,可能會存在一種情況,如果傳入是空值,會導(dǎo)致報錯。所以需要引入一個函子,Maybe函子,只需要引入一個三則,這樣我們就能夠過濾空值,防止報錯。

          class Maybe {
              private val:any;
              private constructor (val: any) {
                 this.val = val;
              }
              public static of(val){
                  return new Maybe(val);
              }
              public map(Fn){
                  return this.val ? Maybe.of(Fn(this.val)) : Maybe.of(null);
              }
              
          }
          Maybe.of(5).map(add5).map(double)

          好的,我們現(xiàn)在已經(jīng)得到了一個可以過濾空值的函數(shù),但是我們現(xiàn)在在執(zhí)行完調(diào)用后,我們獲得的是一個什么呢,是一個對象對吧,我們還需要把值取出來,所以需要添加一個取值的方法

          class Maybe {
             private val:any;
             private constructor (val: any) {
                this.val = val;
             }
             public static of(val){
                 return new Maybe(val);
             }
             public map(Fn){
                 return this.val ? Maybe.of(Fn(this.val)) : Maybe.of(null);
             }
             public join(){
                 return this.val;
             }
          }
          Maybe.of(5).map(add5).map(double).join()

          好了,我們現(xiàn)在可以拿到值了,但是,如果may層次太高,我們是不是需要像洋蔥一樣去剝開他的心,那更簡單的是什么呢,在我們需要的時候去剝開它。

          class Maybe {
              private val:any;
              private constructor (val: any) {
                 this.val = val;
              }
              public static of(val){
                  return new Maybe(val);
              }
              public map(Fn){
                  return this.val ? Maybe.of(Fn(this.val)) : Maybe.of(null);
              }
              public join(){
                  return this.val;
              }
              public chain(Fn) {
                  return this.map(Fn).join();
              }
          }
          Maybe.of(5).map(add5).chain(Maybe.of(double))

          IO monad,一個例子

          如果我們要寫一個對dom的讀寫操作,將一個文本轉(zhuǎn)換為大寫,先定義一下以下方法

          const $ = (id: string) => <HTMLElement>document.querySelector(`#${id}`);
          const read = (id: string) => $(id).value;
          const write = (id: string) => (text: string) => $(id).textContent = text;
          // not pure,因為函數(shù)中操作了dom,對外部進(jìn)行了改變
          function syncInputToOutput(idInput: string, idOutput: string{
            const inputValue = read(idInput);
            const outputValue = inputValue.toUpperCase();
            write(idOutput, outputValue);
          }
          export default class IO<T> {
            private effectFn: () => T;

            constructor(effectFn: () => T) {
              this.effectFn = effectFn;
            }

            bind<U>(transform: (value: T) => IO<U>) {
              return new IO<U>(() => transform(this.effectFn()).run());
            }

            run()T {
              return this.effectFn();
            }
          }

          const read = (id: string) =>
           new IO<string>(() => $(id).value);
          const write = (id: string) =>
           (text: string) => new IO<string>(() => $(id).textContent = text);

          function syncInputToOutput(idInput: string, idOutput: string) {
            read(idInput)
              .bind((value: string) => new IO<string>(() => value.toUpperCase()))
              .bind(write(idOutput)))
              .run();
          }

          延伸閱讀

          • 函數(shù)式語言在深度學(xué)習(xí)領(lǐng)域應(yīng)用很廣泛,因為函數(shù)式與深度學(xué)習(xí)模型的契合度很高,The Beauty of Functional Languages in Deep Learning?—?Clojure and Haskell[8] 。深度學(xué)習(xí)的計算模型本質(zhì)上是數(shù)學(xué)模型,而數(shù)學(xué)模型本質(zhì)上和函數(shù)式編程思路是一致的:數(shù)據(jù)不可變且函數(shù)間可以任意組合。這意味著使用函數(shù)式編程語言可以更好的表達(dá)深度學(xué)習(xí)的計算過程,因此更容易理解與維護,同時函數(shù)式語言內(nèi)置的 Immutable 數(shù)據(jù)結(jié)構(gòu)也保障了并發(fā)的安全性。
          • 范疇學(xué)
          • 前端中的 Monad[9]

          References:

          • FP jargon英文[10]FP jargon中文[11]
          • https://github.com/fantasyland/fantasy-land[12]
          • https://github.com/MostlyAdequate/mostly-adequate-guide[13]
          • https://github.com/llh911001/mostly-adequate-guide-chinese[14]

          參考資料

          [1]

          維基: https://zh.wikipedia.org/wiki/純函數(shù)

          [2]

          狀態(tài): https://zh.wikipedia.org/w/index.php?title=程式狀態(tài)&action=edit&redlink=1

          [3]

          函數(shù)副作用: https://zh.wikipedia.org/wiki/函數(shù)副作用

          [4]

          lambda演算: https://zh.wikipedia.org/wiki/Λ演算

          [5]

          圖靈完備: https://zh.wikipedia.org/wiki/圖靈完備性

          [6]

          丘奇數(shù): https://zh.wikipedia.org/wiki/邱奇數(shù)

          [7]

          https://cgnail.github.io/academic/lambda-1/: https://cgnail.github.io/academic/lambda-1/

          [8]

          The Beauty of Functional Languages in Deep Learning?—?Clojure and Haskell: https://www.welcometothejungle.co/fr/articles/btc-deep-learning-clojure-haskell

          [9]

          前端中的 Monad: https://zhuanlan.zhihu.com/p/47130217

          [10]

          FP jargon英文: https://github.com/hemanth/functional-programming-jargon#partial-application

          [11]

          FP jargon中文: https://github.com/shfshanyue/fp-jargon-zh

          [12]

          https://github.com/fantasyland/fantasy-land: https://github.com/fantasyland/fantasy-land

          [13]

          https://github.com/MostlyAdequate/mostly-adequate-guide: https://github.com/MostlyAdequate/mostly-adequate-guide

          [14]

          https://github.com/llh911001/mostly-adequate-guide-chinese: https://github.com/llh911001/mostly-adequate-guide-chinese

          ?? 謝謝支持

          以上便是本次分享的全部內(nèi)容,希望對你有所幫助^_^

          喜歡的話別忘了 分享、點贊、收藏 三連哦~。

          歡迎關(guān)注公眾號  前端Sharing


          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐在线视频国产 | 97久久一区二区三区 | 国产123区在线观看 | 人妻日韩精品中文字幕 | 91又大又粗又爽 |