# Curry

# 實做 curry(1)

function curry(fn, ARITY = fn.length) {
  let args = [];
  return function curried(nextArg) {
    args = [...args, nextArg];
    if (args.length >= ARITY) {
      return fn(...args);
    } else {
      return curried;
    }
  };
}
1
2
3
4
5
6
7
8
9
10
11

function.length 補充

const fn = (x, y, z) => x + y + z;
console.log(fn.length); // 3

const fn = (x) => (y) => (z) => x + y + z;
console.log(fn.length); // 1
1
2
3
4
5

其中的 line 5 ~ 9:設定限制條件(檢查是否收集到足夠的參數),如果未達,返回一個 function curried,等待滿足條件時,才返回結果。

# 測試 curry function

const sum = curry((x, y, z) => x + y + z);
sum(1)(2)(3); // 6

sum(1)(2)(3); // Uncaught TypeError: sum(...) is not a function
1
2
3
4

因為是與第一次呼叫時共用相同的變數(args),第二次的第一個參數傳入後,並沒有返回一個 curried function,而是第一次的結果值(6),故報 not a function 錯誤。

# 實做 curry(2)


 








 


function curry(fn, ARITY = fn.length) {
  return (function nextCurried(prevArgs) {
    return function curried(nextArg) {
      var args = [...prevArgs, nextArg];
      if (args.length >= ARITY) {
        return fn(...args);
      } else {
        return nextCurried(args);
      }
    };
  })([]);
}
1
2
3
4
5
6
7
8
9
10
11
12

使用回傳 IIFE,使得每次的 curried 開始時都是最新的狀態。

# 測試 curry function

const sum = curry((x, y, z) => x + y + z);
sum(1)(2)(3); // 6

sum(1)(2)(3); // 6
1
2
3
4

# 改寫 ES6 版本

const curry = (fn, ARITY = fn.length, nextCurried) =>
  (nextCurried = (prevArgs) => (nextArg) => {
    const args = [...prevArgs, nextArg];

    if (args.length >= ARITY) {
      return fn(...args);
    } else {
      return nextCurried(args);
    }
  })([]);
1
2
3
4
5
6
7
8
9
10
  1. 初始 [] 做為 prevArgs,收集已傳入的參數
  2. 多傳入 nextCurried 做為遞迴的具名 arrow function
  3. 每當傳入參數時,便會回傳 nextCurried(),直到收集到足夠的實參,就利用這些實參,呼叫原函式 fn

# 使用限制

如果 length 不明確的函式:包含預設參數、destructing、或不定長度參數 ...args,便要指定明確的參數個數傳入。

function sum(...args) {
  var sum = 0;
  for (let i = 0; i < args.length; i++) {
    sum += args[i];
  }
  return sum;
}

sum(1, 2, 3, 4, 5);

// 運用 Currying
// 因為是不定長度參數 ...args,需指定個數
var curriedSum = curry(sum, 5);

curriedSum(1)(2)(3)(4)(5); // 15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# curry and map

運用 Ramda 將一組數列都加上指定的數值。

const add = (x, y) => x + y;

// 作法ㄧ
// 將 curry 和指定的數值,撰寫於 map 的 callback 中
R.map(R.curry(add)(3))([30, 55, 42, 87, 66]);

// 作法二
// 將 curry 函式另行包裝,map 的 callback 中,加入包裝後的 curry 和數值
const addX = R.curry(add);

R.map(addX(3))([30, 55, 42, 87, 66]);
1
2
3
4
5
6
7
8
9
10
11

# 參數順序調整

因為傳入 curry 的 fn 參數,需要有順序性,而運用於解構參數,且維持 arity = 1 的情況就會有問題。

const move = ({ x = 0, y = 0, z }) => [x, y, z];

R.curry(move)({ x: 1 })({ y: 2 })({ z: 3 }); // Uncaught TypeError: R.curry(...)(...) is not a function
1
2
3

# 改寫 curry function

function curryProps(fn, arity = 1) {
  return (function nextCurried(prevArgsObj) {
    return function curried(nextArgObj = {}) {
      var [key] = Object.keys(nextArgObj);
      var allArgsObj = Object.assign({}, prevArgsObj, {
        [key]: nextArgObj[key],
      });

      if (Object.keys(allArgsObj).length >= arity) {
        return fn(allArgsObj);
      } else {
        return nextCurried(allArgsObj);
      }
    };
  })({});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function move({ x = 0, y = 0, z } = {}) {
  return [x, y, z];
}

// 不用再煩惱傳入的順序,和達到 arity = 1
// 一次只傳入一個屬性的物件
curryProps(move, 3)({ x: 2 })({ z: 7 })({ y: 3 }); // [2, 3, 7]
1
2
3
4
5
6
7

# uncurry

將 currying 轉為 uncurring。

傳入的 fn 需為 curry function

const uncurry = (fn) => (...args) => {
  let ret = fn;

  for (let arg of args) {
    // 內部實做 curry,每一個的回傳值都是 curry,直到參數傳完
    ret = ret(arg);
  }

  return ret;
};
1
2
3
4
5
6
7
8
9
10

參考 Functional-Light-JS (opens new window)

# 參考

Currying (opens new window)

再探 Currying 柯里化 (opens new window)

Last Updated: 2021/2/25 上午8:00:30