從菜場算帳想到的Function Currying

買菜引起的思考

前幾天去買菜,老爸說爲啥稱重的時候,老闆一件一件往秤上放,嘴裏唸叨單價,最後就報總價了。是稱了每種商品的價錢,最後心算的總價仍是機器統計的總價?我說確定是機器統計的,就是把數據暫時存起來,最後相加唄。這小功能二十年前就有了吧( ╯□╰ )?!javascript

隨後我去淘寶搜索了一下,電子秤價格幾十塊到一兩百,都有置零和累計功能。 java

那麼累計功能不就是個最後統計求和嗎,這讓我想到了函數柯里化,最簡單的理解就是一個函數接受參數,最開始不求值,而是到最後才求值(例如參數爲空的時候)。咱們能夠直接寫個具備這個特徵的求值函數,這個函數應該會用到閉包去暫存以前輸入的參數,結合菜場算帳這個例子就很容易理解。typescript

/** * * @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 函數具備這個功能吧。app

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,或者其餘功能函數。函數

相關文章
相關標籤/搜索