买菜引发的思考
前几天去买菜,老爸说为啥称重的时候,老板一件一件往秤上放,嘴里念叨单价,最后就报总价了。是称了每种商品的价钱,最后心算的总价还是机器统计的总价?我说肯定是机器统计的,就是把数据暂时存起来,最后相加呗。这小功能二十年前就有了吧( ╯□╰ )?!
随后我去淘宝搜索了一下,电子秤价格几十块到一两百,都有置零和累计功能。
那么累计功能不就是个最后统计求和吗,这让我想到了函数柯里化,最简单的理解就是一个函数接受参数,最开始不求值,而是到最后才求值(例如参数为空的时候)。我们可以直接写个具有这个特征的求值函数,这个函数应该会用到闭包去暂存之前输入的参数,结合菜场算账这个例子就很容易理解。
/**
*
* @param fn input Function to be currified. when fn has no param?(you can define your call condition), fn would be really called.
* otherwise, cache its args in closure.
*/
function currify(fn: Function) {
let args: any[] = [];
return function(arg?:any) { // 这就是我们currify 之后的函数定义,在参数不为空的时候存起来,参数为空就call fn根据所有参数求值。
if (arguments.length === 0) {
console.log(`ready2call fn with args ${args}`);
return fn.call(this, args);
} else {
[].push.apply(args, arguments);
}
}
}
function sum(prices: number[]): number {
return prices.reduce((prev: number, cur: number) => {
return prev + cur;
}, 0);
}
let currifiedSum = currify(sum); // sum 求和函数就是fn,被currify之后新函数具有最后求值的特性
currifiedSum(10);
currifiedSum(20);
currifiedSum(40);
console.warn(currifiedSum());
复制代码
这个currify函数挺有意思的,实际上类似于装饰器,让新的currifiedSum 函数具有了最后求值的功能,这是sum函数原来不具有的特性。结合typescript的装饰器,其实可以写成更简洁的方式:
@currying()
sum(prices: number[]): number {
// 函数定义
}
复制代码
扩展每阶段求和
那么之前这个需求比较简单,就是参数为空统计求和。那实际的电子秤是在每类物品称量阶段接受两个参数,一个是物品单价(price),一个是物品重量(weight),计算当前物品的价钱。在某顾客所有物品称重结束时点击累计按钮,触发统计功能。
那简单修改下,让现有的sum 函数具有这个功能吧。
function extCurrify(fn: Function) {
let args: any[] = [];
return function(arg?: any, arg2?: any) {
if (arguments.length === 0) {
console.log(`ready2call fn with args ${args}`);
return fn.call(this, args);
}
if (arguments.length === 2) {
// if has weight & price
[].push.call(args, calcPrice.call(this, arguments[0], arguments[1]));
} else {
[].push.apply(args, arguments);
}
}
}
function calcPrice(input: number, price: number): number {
console.warn(`cur price: ${input * price}`);
return input * price;
}
let currifiedSum = extCurrify(sum);
currifiedSum(10, 2.2); // weight & price
currifiedSum(1, 30);
currifiedSum(2, 2);
console.warn(currifiedSum());
复制代码
这样加个条件,就可以支持每个阶段再做个乘法,最后求和。虽然破坏了柯里化函数的通用性结构。 代码比较简单,但是我觉得这个生活例子很有趣,好理解,所以写下来。其中闭包、高阶函数的应用,可以达成装饰器的设计模式,在尽量不破坏currify函数的通用性前提下,把逻辑代码单独出来为 sum,或者其他功能函数。