阅读 217

从菜场算账想到的Function Currying

买菜引发的思考

前几天去买菜,老爸说为啥称重的时候,老板一件一件往秤上放,嘴里念叨单价,最后就报总价了。是称了每种商品的价钱,最后心算的总价还是机器统计的总价?我说肯定是机器统计的,就是把数据暂时存起来,最后相加呗。这小功能二十年前就有了吧( ╯□╰ )?!

随后我去淘宝搜索了一下,电子秤价格几十块到一两百,都有置零和累计功能。

那么累计功能不就是个最后统计求和吗,这让我想到了函数柯里化,最简单的理解就是一个函数接受参数,最开始不求值,而是到最后才求值(例如参数为空的时候)。我们可以直接写个具有这个特征的求值函数,这个函数应该会用到闭包去暂存之前输入的参数,结合菜场算账这个例子就很容易理解。

/**
 * 
 * @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,或者其他功能函数。

关注下面的标签,发现更多相似文章
评论