Skip to content

用 JS 理解函数柯里化

一句话解释函数柯里化:让所有函数只接受一个参数。

单一参数

例如,未来的 JS 可能会支持的 pipe 操作:

javascript
function doubleSay (str) {
  return str + ", " + str
}
function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1)
}
function exclaim (str) {
  return str + '!'
}
let result = "hello"
    |> doubleSay
    |> capitalize
    |> exclaim
result  // "Hello, hello!"

或者使用 Ramda.js:

javascript
const say = Ramda.compose(doubleSay, capitalize, exclaim)
say('hello')  // 'Hello, hello!'

如何让单参数函数支持两个参数

可以用对象:

javascript
const add = ({a, b}) => a + b
add({ a: 1, b: 2 })

或者用箭头函数和闭包来实现:

javascript
const add = a => b => a + b
add(1)(2)

柯里化一个函数

面试题

如何把一个三参数的函数 add(1, 2, 3) 变成 curriedAdd(1)(2)(3) 的形式?

答:

javascript
const curriedAdd = a => b => c => a+b+c

面试题升级

写一个 currify 函数,能将任意接受固定个数的参数的函数,变成单一参数的函数?

假设有函数:

javascript
const addTwo = (a, b) => a+b
const addThree = (a, b, c) => a+b+c
const addFour = (a, b, c, d) => a+b+c+d

请实现:

javascript
const newAddTwo = currify(addTwo)
const newAddThree = currify(addThree)
const newAddFour = currify(addFour)
newAddTwo(1)(2)  // 3
newAddThree(1)(2)(3)  // 6
newAddFour(1)(2)(3)(4)  // 10

答:

javascript
const currify = (fn, params = []) => {
  return (arg) => {
    params.push(arg)
    if(fn.length === params.length) {
      return fn(...params)
    } else {
      return currify(fn, params)
    }
  }
}

const newAddTwo = currify(addTwo)
const newAddThree = currify(addThree)
console.log(newAddTwo(1)(2))  // 3
console.log(newAddThree(1)(2)(3))  // 6

解决一个 BUG

执行以下代码,就会出现一个报错:

javascript
const newAddTwo = currify(addTwo)
console.log(newAddTwo(1))  // 返回一个函数
console.log(newAddTwo(1)(2))

// error TypeError: newAddTwo(...) is not a function

因为重复使用了同一个 params 数组来储存传入的参数,可以在递归的时候都生成一个新的 newParams 来解决:

javascript
const currify = (fn, params = []) => {
  return (arg) => {
    const newParams = params.concat(arg)
    if(fn.length === newParams.length) {
      return fn(...newParams)
    } else {
      return currify(fn, newParams)
    }
  }
}

const newAddTwo = currify(addTwo)
const newAddThree = currify(addThree)
console.log(newAddTwo(1))  // 返回一个函数
console.log(newAddTwo(1)(2))  // 3
console.log(newAddThree(1)(2)(3))  // 6

面试题再升级

修改 currify 函数,使得能够支持以下写法:

javascript
const newAddFour = currify(addFour)
newAddFour(1)(2, 3)(4)  // 10

答:

javascript
const currify = (fn, params = []) => {
  return (...args) => {
    const newParams = params.concat(args)
    if(fn.length === newParams.length) {
      return fn(...newParams)
    } else {
      return currify(fn, newParams)
    }
  }
}

const newAddFour = currify(addFour)
console.log(newAddFour(1)(2, 3)(4))  // 10

最终代码优化

javascript
const currify = (fn, params = []) =>
  (...args) =>
    fn.length === params.concat(args).length
      ? fn(...params.concat(args))
      : currify(fn, params.concat(args))