【函数式编程】柯里化与偏应用(JavaScript)

1. 一元函数、多元函数和变参函数

一元函数、多元函数

var fx = function(x) {
	return x * x
}

var fxyz = function(x, y, z) {
	return x + y + fx(z)
}

console.log(fx(2))
console.log(fxyz(1, 2, 3))

变参函数arguments

var add = function(x) {
	let sum = 0
	for (const value of arguments) {
		sum += value
	}
	return sum
}
console.log(add(1, 2, 3))

扩展运算符…

var count = function(x, ...variadic) {
	return variadic.length
}
console.log(count(1, 2, 3, 4))

2. 柯里化(Currying)

柯里化是把一个多参数函数转换为一个嵌套的一元函数的过程

const add = (x, y) => x + y
const addCurried = x => y => x + y;
addCurried(3)(4)

二元curry函数

// 二元函数的柯里化
// const add = (x, y) => x + y
const curry = (binaryFn) => ((firstArg) => (secondArg) => binaryFn(firstArg, secondArg))
let autoCurriedAdd = curry((x, y) => x + y)
console.log(autoCurriedAdd(2)(3))

分析

  1. curry(add)返回的是一个函数(firstArg) => (secondArg) => binaryFn(firstArg, secondArg)
  2. curry(add)(2)返回的是一个函数(secondArg) => binaryFn(firstArg, secondArg)
  3. curry(add)(2)(3)返回的是binaryFn(2, 3)

柯里化用例

// 表格函数
// 假设我们想要实现这样的函数
// const tableOf2 = (y) => 2 * y
// const tableOf3 = (y) => 3 * y
// const tableOf4 = (y) => 4 * y

const genericTable = (x, y) => x * y
// genericTable(2, y) 即为 tableOf2
// genericTable(3, y) 即为 tableOf3
// genericTable(4, y) 即为 tableOf4

const tableOf2 = curry(genericTable)(2)
const tableOf3 = curry(genericTable)(3)
const tableOf4 = curry(genericTable)(4)

console.log("Tables via currying")
console.log("2 * 2 =", tableOf2(2))
console.log("2 * 3 =", tableOf2(3))
console.log("2 * 4 =", tableOf2(4))
console.log("3 * 2 =", tableOf3(2))
console.log("3 * 3 =", tableOf3(3))
console.log("3 * 4 =", tableOf3(4))
console.log("4 * 2 =", tableOf4(2))
console.log("4 * 3 =", tableOf4(3))
console.log("4 * 4 =", tableOf4(4))

多元curry函数

const multiCurry = (fn) => {
	if (typeof fn !== 'function') {
		throw Error("No function provided")
	}
	return function curriedFn(...args) {
		return fn.apply(null, args)
	}
}

const multiply = (x, y, z) => x * y * z
console.log(multiCurry(multiply)(1, 2, 3))
console.log(multiCurry(multiply)(1, 2, 0))

分析

  1. multiCurry(multiply)返回一个函数

    function curriedFn(...args) {
        return fn.apply(null, args)
    }
    
  2. curriedFn(1, 2, 3)返回multiply.apply(null, [1, 2, 3])

  3. multiply.apply(null, [1, 2, 3])等价于multiply(1, 2, 3)

转换为嵌套的一元函数

const curryN =(fn) => {
    if(typeof fn !== 'function'){
        throw Error('No function provided');
    }

    return function curriedFn(...args){
      
      //make it bold
      if(args.length < fn.length){
        return function(){
          return curriedFn.apply(null, args.concat( [].slice.call(arguments) ));
        };
      }
      //make it bold

      return fn.apply(null, args);
    };
};
const multiply = (x, y, z) => x * y * z
curryN(multiply)(3)(2)(1)

3. 柯里化实战

在数组中查找数字

let match = curry(function(expr, str) {
	return str.match(expr)
})

let hasNumber = match(/[0-9]+/)

let filter = curry(function(f, ary) {
	return ary.filter(f)
})

let findNumbersInArray = filter(hasNumber)
findNumbersInArray(['js', 'number1'])	
> [ 'number1' ]

分析

let arr = ['js', 'number1']
1. findNumbersInArray(ary) 也就是 filter(match(/[0-9]+/))(arr)
2. match(/[0-9]+/) 也就是 (str) => str.match(/[0-9]+/)
3. filter((str) => str.match(/[0-9]+/))(arr)
> "number1".match(/[0-9]+/)
[ '1', index: 6, input: 'number1', groups: undefined ]
> "number".match(/[0-9]+/)
null

> arr.filter((str) => str.match(/[0-9]+/))
[ 'number1' ]
// 检验上述的分析过程
filter((str) => str.match(/[0-9]+/))(['js', 'number2'])
[ 'number2' ]

求数组的平方

// 原始使用方式
> let arr = [1, 2, 3]
> arr.map(x => x * x)
[ 1, 4, 9 ]
// 求数组的平方
let map = curry((f, ary) => ary.map(f))
let squareAll = map(x => x * x)
let arr = [1, 2, 3]
squareAll(arr)
// 分析
squareAll(arr) => map(x => x * x)(arr) => arr.map(x => x * x)

4. 柯里化不能做的

setTimeout(() => console.log("Do X task"), 10)
setTimeout(() => console.log("Do Y task"), 10)

const setTimeoutWrapper = (time, fn) => setTimeout(fn, time)
const delayTenMs = curry(setTimeoutWrapper)(10)
delayTenMs(() => console.log("Do X task"))
delayTenMs(() => console.log("Do Y task"))

因为curry函数应用参数列表的顺序是从最左到最右,因此隐藏10需要setTimeoutWrapper额外的开销!

5. 偏应用

实现偏应用

const partial = function (fn,...partialArgs){
  let args = partialArgs.slice(0);
  return function(...fullArguments) {
    let arg = 0;
    for (let i = 0; i < args.length && arg < fullArguments.length; i++) {
      if (args[i] === undefined) {
        args[i] = fullArguments[arg++];
        }
      }
      return fn.apply(this, args);
  };
};

1. setTimeout实现偏应用

delayTenMs = partial(setTimeout, undefined, 10)
delayTenMs(() => console.log("Do Y task"))
// 分析
1. partial(setTimeout, undefined, 10) 返回 function(...fullArguments) {...}
2. fullArguments = [() => console.log("Do Y task")], 因此 fullArguments.length = 1
3. args[0] = fullArguments[0]
4. args = [() => console.log("Do Y task"), 10]
5. 最后返回 setTimeout(() => console.log("Do Y task"), 10)  

2. JSON.stringify实现偏应用

> let obj = {foo: 'bar', bar: 'foo'}
undefined
> JSON.stringify(obj, null, 2)
'{\n  "foo": "bar",\n  "bar": "foo"\n}'
let obj = {foo: 'bar', bar: 'foo'}
let prettyPrintJson = partial(JSON.stringify, undefined, null, 2)
console.log(prettyPrintJson(obj))
{
  "foo": "bar",
  "bar": "foo"
}

bug

let obj1 = {foo: 'bar'}
console.log(prettyPrintJson(obj1))
{
  "foo": "bar",
  "bar": "foo"
}
obj['bar'] = undefined
console.log(prettyPrintJson(obj))
{
  "foo": "bar"
}

注意数组传递的是引用!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值