函數(shù)式編程

什么是函數(shù)式編程

函數(shù)式編程的概念誕生在二十世紀(jì)五十年代,近些年函數(shù)式編程獲得越來越多的關(guān)注,很多語言加入了函數(shù)式編程的支持。比如java 8 加入了lambda表達(dá)式。

函數(shù)式編程是一種編程范式,也就是如何編寫程序的方法論。通過組合函數(shù)的方式來編寫程序。函數(shù)式編程主張聲明式編程而非命令式編程。

函數(shù)式編程中的函數(shù)這個術(shù)語是指數(shù)學(xué)中的函數(shù),即自變量的映射y=f(x)。也就是說函數(shù)的值僅取決于函數(shù)的參數(shù),不依賴其它狀態(tài)。我們要使用數(shù)學(xué)函數(shù)的思想來理解函數(shù)式編程。

函數(shù)式編程的理論基礎(chǔ)是lambda演算,用函數(shù)組合的方式來描述計算過程,即一個問題如果能用一套函數(shù)組合的算法來表達(dá)那么這個問題是可計算的。

函數(shù)式編程的主要思想是把運算過程盡量寫成一系列的函數(shù)調(diào)用。比如:

(a+b)*c-d

函數(shù)式編程要求運算過程定義為不同的函數(shù):

subtract(multiply(add(a,b),c),d)

聲明式和命令式

函數(shù)式編程主張聲明式編程和編寫抽象的代碼。假設(shè)有一個數(shù)組,你想遍歷它并打印到控制臺。命令式寫法:

//命令寫法
var array = [1, 2, 3];
for (let index = 0; index < array.length; index++) {
  console.log(array[index]);
}

為了解決問題,我們告訴程序該如何做:獲取數(shù)組長度,循環(huán)數(shù)組,用索引獲取每個元素。命令式編程主張告訴編譯器若何做。
在聲明式編程中,我們要告訴編譯器做什么,而不是如何做。如何做被抽象到普通函數(shù)中。聲明式寫法:

var array = [1, 2, 3];
// 聲明式寫法
array.forEach((element) => console.log(element));

我們使用了如何做的抽象函數(shù)forEach,如此可以讓開發(fā)者只關(guān)心做什么的部分。把操作抽象為函數(shù)是函數(shù)式編程的核心思想。我們把循環(huán)的操作抽象為函數(shù),以便在需要時可以重用:

const forEach = (array, fn) => {
  for (let i = 0; i < array.length; i++) fn(array[i]);
};

函數(shù)式編程5個特點

函數(shù)是一等公民

函數(shù)和普通數(shù)據(jù)類型一樣,可以作為參數(shù)傳遞,可以賦值,可以作為函數(shù)的返回值。

函數(shù)沒有副作用

對同樣的輸入,總是返回相同的輸出,不能修改外部變量的函數(shù)稱為純函數(shù),否則稱該函數(shù)是有副作用的。一個沒有副作用的函數(shù):

const double = (value) => value * 2;

一個有副作用的函數(shù):

let discount = 0.8;
let price = (value) => discount * value;
// price依賴外部變量discount

price函數(shù)依賴外部變量discount,那么:

price(10) === 8

如果discount變化了,那么:

discount = 0.6;
price(10) === 6;

對同樣的輸入輸出不同那么price函數(shù)是有副作用的。

純函數(shù)不應(yīng)該修改外部變量,例如:

// 修改外部變量
var global = "value";
var badFunction = (value) => {
  global = "value2";
  return value * 2;
};

badFunction函數(shù)修改了global變量,調(diào)用badFunction函數(shù)則影響了其它函數(shù)的行為。

引用透明性

對于同樣的輸入都將返回相同的值,函數(shù)的這一屬性被稱為引用透明性。例如:

var double = (value) => value * 2;
// double(2) 可以用 4 替換

利用引用透明性我們可以緩存函數(shù)的值,比如我們有一個計算階乘的函數(shù)factorial,我們知道5的階乘是120,當(dāng)?shù)诙斡嬎?的階乘時不用重新計算了,直接使用緩存的值即可。

數(shù)據(jù)是不可變的

在純的函數(shù)式編程語言中,數(shù)據(jù)是不可變的,沒有變量的概念。所有的數(shù)據(jù)一旦產(chǎn)生就不能改變它的值,如果要改變只能生成新的數(shù)據(jù)。

只使用表達(dá)式不使用語句

函數(shù)式編程要求只使用表達(dá)式不使用語句,也就是每一步都是單純的運算,并且有返回值。

函數(shù)式編程舉例

打印數(shù)組中偶數(shù)的值

命令式編程:

// 查找列表中的偶數(shù)
var array = [1, 2, 3, 4, 8];
for (let index = 0; index < array.length; index++) {
  const element = array[index];
  if (element % 2 === 0) {
    console.log(element);
  }
}

函數(shù)式編程要把操作過程抽象到函數(shù)中,把循環(huán)過程抽象到forEach 函數(shù)中,判斷是否為偶數(shù)if (element % 2 === 0){}抽象到unless函數(shù)中,調(diào)用:

const forEach = (array, fn) => {
  for (let index = 0; index < array.length; index++) {
    const element = array[index];
    fn(element);
  }
};

const unless = (num, fn) => {
  if (num % 2 === 0) {
    fn();
  }
};

forEach(array, (element) => {
  unless(element, () => console.log(element));
});

100以內(nèi)的奇數(shù)

// 命令式寫法
for (let index = 0; index < 100; index++) {
  if (index % 2) {
    console.log(index);
  }
}

函數(shù)式寫法:

function times(num, fn) {
  for (let index = 0; index < num; index++) {
    fn(index);
  }
}

function unless(predicate, fn) {
  if (predicate) {
    fn();
  }
}
// 100 以內(nèi)的奇數(shù)
times(100, (index) => {
  unless(index % 2, () => {
    console.log(index);
  });
});

數(shù)組函數(shù)式編程綜合應(yīng)用

統(tǒng)計 books 評價 good 和 excellent 的數(shù)量,數(shù)據(jù)結(jié)構(gòu)如下:

let apressBooks2 = [
  {
    name: "beginners",
    bookDetails: [
      {
        id: 111,
        title: "C# 6.0",
        author: "ANDREW TROELSEN",
        rating: [4.7],
        reviews: [{ good: 4, excellent: 12 }],
      },
      {
        id: 222,
        title: "Efficient Learning Machines",
        author: "Rahul Khanna",
        rating: [4.5],
        reviews: [],
      },
    ],
  },
  {
    name: "pro",
    bookDetails: [
      {
        id: 333,
        title: "Pro AngularJS",
        author: "Adam Freeman",
        rating: [4.0],
        reviews: [],
      },
      {
        id: 444,
        title: "Pro ASP.NET",
        author: "Adam Freeman",
        rating: [4.2],
        reviews: [{ good: 14, excellent: 12 }],
      },
    ],
  },
];

過程式寫法:

function p() {
  const allBooks = apressBooks2.map((e) => e.bookDetails);
  const concatBooks = [];
  allBooks.forEach((e) => concatBooks.push(...e));
  const allPreviews = concatBooks.map((e) => e.reviews);
  const concatPreviews = allPreviews.filter((e) => e.length).map((e) => e[0]);
  return concatPreviews.reduce(
    (acc, e) => [acc[0] + e.good, acc[1] + e.excellent],
    [0, 0]
  );
}

函數(shù)式寫法:

// 省略reduce, concatAll, map等Api
function fn() {
  return reduce(
    concatAll(
      map(concatAll(map(apressBooks2, (e) => e.bookDetails)), (e) => e.reviews)
    ),
    (acc, e) => [acc[0] + e.good, acc[1] + e.excellent],
    [0, 0]
  );
}

函數(shù)式庫

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容