函數柯里化&偏函數

前言

最近幾天在看高程,對於其中的概念有些疑惑,所以找了相關的教程,但願本身可以瞭解它,而且有可能運用它。本文主要參考的是冴羽的博客,結合自身能理解的方式來熟悉這兩個概念。javascript

柯里化

什麼是柯里化呢?來自於冴羽的博客-JavaScript專題之函數柯里化html

在數學和計算機科學中,柯里化是一種將使用多個參數的一個函數轉換成一系列使用一個參數的函數的技術。java

舉個例子

本例子來自於Fun Fun Function-YouTubegit

先舉一個普通函數

let dragon = (name, size, element) => {
  return `${name} is a ${size} dragon that breathes ${element}!`;
};

console.log(dragon("Karo", "large", "ice"));
// Karo is a large dragon that breathes ice!
複製代碼

模擬柯里化方式

let dragon = name => size => element =>
  `${name} is a ${size} dragon that breathes ${element}!`;
console.log(dragon('Karo')('large')('ice'));
// Karo is a large dragon that breathes ice!
複製代碼

高程中柯里化的實現方式

function curry(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return fn.apply(null, finalArgs);
  }
}

let dragon = (name, size, element) => {
  return `${name} is a ${size} dragon that breathes ${element}!`;
};
dragon = curry(dragon);
let fluffykinsDragon = dragon('fluffykins'); // curyy函數只能保存一個參數就返回了
// fluffykins is a undefined dragon that breathes undefined!
// 若是執行下面語句就會報錯,
// let tinyDragon = fluffykinsDragon("tiny"); 報錯
複製代碼

柯里化函數如何實現呢?

藉助上面的例子,咱們開始從初版寫起github

柯里化初版

// 初版
function curry(fn) {
  var args = [];
  return function() {
    args = args.concat([].slice.call(arguments));
    return function() {
      args = args.concat([].slice.call(arguments));
      return function() {
        args = args.concat([].slice.call(arguments));
        return fn.apply(null, args);
      }
    }
  };
}
複製代碼

測試一下app

// 測試
let dragon = (name, size, element) => {
  return `${name} is a ${size} dragon that breathes ${element}!`;
};
dragon = curry(dragon);
let fluffykinsDragon = dragon("fluffykins");
let tinyDragon = fluffykinsDragon("tiny");
console.log(tinyDragon("ice"));
// fluffykins is a tiny dragon that breathes ice!
複製代碼

若是參數過多,那麼就會無限嵌套,所以第二版用遞歸優化一下函數

柯里化第二版

// 第二版
function curry(fn, args, length) {
  length = length || fn.length;
  args = args || [];
  return function() {
    args = args.concat([].slice.call(arguments));
    if (arguments.length < length) {
      return curry(fn, args, length - arguments.length);
    }
    return fn.apply(this, args);
  }
}
複製代碼

咱們發現curry(fn, args, length - 1)有三個參數,咱們利用高程中的例子當成中間函數能夠再優化一下,因而有了第三版測試

柯里化第三版

// 第三版
function sub_curry(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return fn.apply(null, finalArgs);
  }
}

function curry(fn, length) {
  length = length || fn.length;
  return function() {
    if (arguments.length < length) {
      var args = [fn].concat([].slice.call(arguments));
      return curry(sub_curry.apply(this, args), length - arguments.length);
    }
    return fn.apply(this, arguments);
  };
}
複製代碼

使用ES6改寫一下,優化一下

function sub_curry(fn, ...args) {
  return (...args1) => fn(...args, ...args1);
}

function curry(fn, length) {
  length = length || fn.length;
  return (...args) => {
    if (args.length < length) {
      return curry(sub_curry(fn, ...args), length - args.length);
    }
    return fn(...args);
  };
}
複製代碼

穿插一下1

以前我把args參數進行柯里化了,如今除去length參數的另外一種寫法優化

function curry(fn, args) {
  var length = fn.length;

  args = args || [];
  return function() {
    var _args = args.concat([].slice.call(arguments));
    if (_args.length < length) {
      return curry.call(this, fn, _args);
    }
    return fn.apply(this, _args);
  };
}
複製代碼

穿插一下2——我看到一種更簡潔的寫法

function curry(fn) {
  return judge = (...args) => {
    return args.length === fn.length ? fn(...args) : (...arg) => judge(...args, ...arg);
  };
}
複製代碼

柯里化第四版

參數含有佔位符ui

// 第四版
function curry(fn, args) {
  var length = fn.length;
  args = args || [];
  return function() {
    var newArgs = [].slice.call(arguments);
    for (var i = 0, len = args.length; i < len; i++) {
      if (args[i] === _) {
        args.splice(i, 1, newArgs.shift());
      }
      if (newArgs.length === 0) break;
    }
    var _args = args.concat(newArgs);
    var _filterArr = _args.filter(ele => ele !== _);
    if (_filterArr.length < length) {
      return curry.call(this, fn, _args);
    }
    return fn.apply(this, _args);
  };
}
複製代碼

測試一下

var fn = curry(function(a, b, c, d, e) {
  console.log([a, b, c, d, e]);
});
var _ = {};
// 輸出的結果都是[1, 2, 3, 4, 5]
fn(1, 2, 3, 4, 5);
fn(_, 2, 3, 4, 5)(1);
fn(1, _, 3, 4, 5)(2);
fn(1, _, 3)(_, 4)(2)(5);
fn(1, _, _, 4)(_, 3)(2)(5);
fn(_, 2)(_, _, 4)(1)(3)(5);
複製代碼

偏函數

什麼是偏函數呢?仍是來自於冴羽的博客

在計算機科學中,局部應用是指固定一個函數的一些參數,而後產生另外一個更小元的函數。

柯里化是將一個多參數函數轉換成多個單參數函數,也就是將一個 n 元函數轉換成 n 個一元函數。

局部應用則是固定一個函數的一個或者多個參數,也就是將一個 n 元函數轉換成一個 n - x 元函數。

偏函數初版

// 也就是高程中的例子
function partial(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return fn.apply(null, finalArgs);
  }
}
複製代碼

偏函數第二版

var _ = {};
function partial(fn) {
  var args = [].slice.call(arguments, 1);
  return function() {
    var len = args.length;
    var _args = [].slice.call(arguments);
    for(var i = 0; i < len; i++) {
      args[i] = args[i] === _ ? _args.shift() : args[i];
      if (_args.length === 0) break;
    }
    args.concat(_args);
    return fn.apply(this, args);
  }
}
複製代碼

測試一下

var subtract = function(a, b, c) {
  return b - a + c;
};
var subFrom20 = partial(subtract, 5, _, _);
console.log(subFrom20(15, 5, 5)); // 15
複製代碼

總結一下

經過教程和本身的理解初步瞭解了柯里化和偏函數,至於具體的使用場景,多是用到了才知道吧。

參考教程

我的博客

Tony's blog

相關文章
相關標籤/搜索