三行代碼實現 JS 柯里化

最近有看到一些柯里化的文章,怎麼說呢,感受很奇怪。一篇是阿里雲的譯文,文章末尾給出了這樣一個 "curry":app

function curry(fn, ...args) {
    return (..._arg) => {
        return fn(...args, ..._arg);
    }
}
複製代碼

做者前面明明例舉了柯里化和部分應用的區別,結果最後說咱們實現下柯里化吧,而後寫了個部分應用……太假了,我忍不住評論了一下:函數

而後今天看到咱們組歡哥的文章,說實話看了一下開頭這段代碼我就不太有耐心看下面具體的分析了:post

// 定義佔位符
var _ = '_';

function magician3 (targetfn, ...preset) {
  var numOfArgs = targetfn.length;
  var nextPos = 0; // 下一個有效輸入位置的索引,能夠是'_',也能夠是preset的結尾

  // 查看是否有足夠的有效參數
  if (preset.filter(arg=> arg !== _).length === numOfArgs) {
    return targetfn.apply(null, preset);
  } else {
    // 返回'helper'函數
    return function (...added) {
      // 循環並將added參數添加到preset參數
      while(added.length > 0) {
        var a = added.shift();
        // 獲取下一個佔位符的位置,能夠是'_'也能夠是preset的末尾
        while (preset[nextPos] !== _ && nextPos < preset.length) {
          nextPos++
        }
        // 更新preset
        preset[nextPos] = a;
        nextPos++;
      }
      // 綁定更新後的preset
      return magician3.call(null, targetfn, ...preset);
    }
  }
}
複製代碼

這是在幹嗎……而後歡哥他們發現了這段代碼有 bug,分析了一通,解決了 bug,美好的青春啊朋友們,出去喝酒蹦迪大保健很差麼,非得這麼揮霍生命麼……ui

在咱們本身實現以前,對柯里化沒什麼概念的同窗能夠看下 wiki(要看英文 wiki,中文 wiki 對柯里化的解釋寫得又亂又不許確,容易和部分應用混淆),簡單來講柯里化就是把一個多參函數轉換成接受單參的一系列函數。它跟部分應用的概念不太同樣,部分應用是把一個多參函數「切」一刀,而柯里化是把函數「切」好多刀,直到中間每一個函數都是單參的,最後獲得的結果就是所謂的柯里化函數(curried function)。在 JS 裏要手寫個 curried function 其實就是手寫個高階函數,沒什麼特別的。那要實現一個通用的 curry,該怎麼作呢,我不是針對誰,我是說上面那兩個實現都在賣萌……阿里雲

const curry = (fn) => {
    if (fn.length <= 1) return fn;
    
    // 這是我一開始的實現
    // 後來發現 rest 是多餘的,下面這樣就好了,fn.length 多處用到,能夠提出來
    // const generator = (args) => (args.length === fn.length ? fn(...args) : arg => generator([...args, arg]));
    const generator = (args, rest) => (!rest ? fn(...args) : arg => generator([...args, arg], rest - 1));
    
    return generator([], fn.length);
};
複製代碼

這不就三行代碼搞定了麼(不算函數聲明),測一下:spa

const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum); 
const res = curriedSum(1)(2)(3) 
console.log(res); // 6

const log = (a, b, c) => {
    console.log(a, b, c);
};
const curriedLog = curry(log);
curriedLog('a')('b')('c'); // a b c
複製代碼

好像沒啥問題吧……emmmmm……歡迎打臉。3d

因而可知,在折騰什麼 curry 什麼 partial application 以前,仍是多琢磨琢磨遞歸這種基本概念,順便附送一個 FP Style 的快排吧,忽然感受我也挺揮霍青春的……rest

const quickSort = (list) => {
    if (!list || !list.length) return [];
    if (list.length === 1) return list;

    const [middle, ...rest] = list;
    const reducer = (acc, x) => (
        x <= middle ? 
        { ...acc, left: [...acc.left, x] } : 
        { ...acc, right: [...acc.right, x] }
    );
    const { left, right } = rest.reduce(reducer, { left: [], right: [] });
    return [...quickSort(left), middle, ...quickSort(right)];
};

const list = [2, 3, 1, 8, 8, 1, 2, 18, 6, 2333];
const sorted = quickSort(list); // [ 1, 1, 2, 2, 3, 6, 8, 8, 18, 2333 ]

複製代碼

PS:評論裏有位大佬給了一個一行的實現:code

const curry = (fn, arr = []) => (...args) => (
  arg => arg.length === fn.length
    ? fn(...arg)
    : curry(fn, arg)
)([...arr, ...args])
複製代碼

我也只能開動小腦筋把個人實現改爲了一行……cdn

// 跟三行的思路是同樣的,就是強行寫到了一行……
const curry = fn =>
    (arg, args = [arg], rest = fn.length - 1) =>
    (rest < 1 ? fn(...args) : newArg => curry(fn)(newArg, [...args, newArg], rest - 1));
複製代碼

對比上面兩個實現,咱們會發現個人實現……敗了,由於我既然有 args,這個 rest 就是多餘的,因此改爲這樣:

// 感受仍是多拆幾行比較好……
const curry = fn =>
    (arg, args = [arg]) =>
    (!fn.length || args.length === fn.length ? fn(...args) : newArg => curry(fn)(newArg, [...args, newArg]));
複製代碼
相關文章
相關標籤/搜索