學習使用Pointfree風格優化代碼 - 函數式編程

前言

什麼是Pointfree風格?中譯過來是無參數風格或者無值風格,意即爲在編寫程序時不關注具體數據以及對象,而只關注的是運算過程。編程

下文將用簡單的例子帶你們瞭解Pointfree風格~函數式編程

命令式編程的問題?

"告訴計算機該怎麼作,詳細的執行步驟"函數

考慮如下需求:小A去水果店去買水果,他想知道水果店裏有存貨而且最貴的水果名稱是什麼?工具

const fruits = [
  { name: 'Apple', price: 4, stock: true },
  { name: 'Peach', price: 14, stock: true },
  { name: 'Grape', price: 30, stock: false },
  { name: 'Pear', price: 6, stock: true },
];
複製代碼

咱們常見的實現方式通常以下:post

// Ex 1
const fruitsHaveStock = fruits.filter(fruit => fruit.stock);
const sortedDescFruits = fruitsHaveStock.sort((a, b) => b.price - a.price);
const mostExpensiveFruitName = sortedDescFruits[0].name;
console.log(mostExpensiveFruitName); // Peach
複製代碼

這個是很明顯的命令式編程,由於大腦會習慣線性的去理解事物,因此咱們天然而然的羅列了這個需求的實現步驟,從而實現了上述的需求。可是這樣的代碼缺點以下:單元測試

  1. 代碼難以複用。
  2. 代碼自上而下,並無組織,從閱讀上須要完整的從上至下閱讀,才能瞭解發生了什麼事。
  3. 能夠觀察到fruits.filter(fruit => fruit.stock);中的fruit參數、fruitsHaveStocksortedDescFruits這些都是Point,換句話說,咱們的程序關注了被操做的數據!

運用聲明式編程和無值風格優化代碼

"告訴計算機作什麼,咱們想要什麼"學習

聲明式編程風格

上述代碼用聲明式的風格重寫一下~測試

// Ex 2
const filter = predicate => list => list.filter(predicate);
const propEq = (key, value) => target => target[key] === value;
const compose = (...funcs) => result => [...funcs]
  .reverse()
  .reduce((result, fn) => fn(result), result);
const sort = func => list => list.sort(func);
const prop = key => target => target[key];
const head = list => list.slice(0, 1).pop();

function getHaveStock(list) {
  return filter(propEq('stock', true))(list);
}

function sortByPriceDesc(list) {
  return sort((a, b) => b.price - a.price)(list);
}

function getName(target) {
  return prop('name')(target);
}

function getMostExpensiveFruitName(list) {
  return compose(
    getName, head, sortByPriceDesc, getHaveStock
  )(list);
}

console.log(getMostExpensiveFruitName(fruits)); // Peach
複製代碼

同窗們對比Ex 1和Ex 2代碼,能夠發現兩點:優化

  1. 我寫了一些通用工具方法filterpropEqcompose等等
  2. 最後compose的時候,能夠明確知道數據流從 getHaveStock -> sortByPriceDesc -> head -> getName。而咱們不須要了解這些函數的細節實現,從這些函數名稱上來看,能夠清晰的知道咱們要對接收的數據進行的操做!

(備註,關於compose方法若是不懂的話,能夠閱讀我以前寫的一篇文章參考哦:Compose & Pipe - 函數式編程)(●´∀`●)ノui

Pointfree風格,不關注數據!

可是問題來了,細心的同窗應該發現了一個問題,雖說咱們改爲了聲明式編程的風格,可是咱們仍是關注了要處理的數據自己(也就是值),什麼意思?

好比這個函數:

function getHaveStock(list) {
  return filter(propEq('stock', true))(list);
}
複製代碼

由於咱們只想關心怎麼運算操做!list參數對於函數自己實現來講,徹底屬於多餘,且也不須要關注的。

因而咱們能夠改寫以下:

const getHaveStock = filter(propEq('stock', true));
複製代碼

其他sortByPriceDesc, getName, getMostExpensiveFruitName同理優化。因而就達到了咱們說的 Pointfree 啦!(由於咱們幹掉了point -> list參數)

固然,說不定有些同窗發發牢騷了:「代碼好像變得更長了...並且還寫了一大堆莫名其妙的工具方法,我纔不想這麼幹呢!」

這位同窗說的有道理!請繼續往下看~

Ramda

一款實用的,專門爲函數式編程風格而設計的JavaScript函數式編程庫

若是決心瞭解JavaScript函數式編程,而且想要用Pointfree風格優化一下代碼,那麼ramda是值得學習的!它幫咱們省掉了上述Ex 2寫的大量工具函數。 官網貼上:ramda

因此,咱們使用ramda改寫一下~

// Ex 3
const { filter, propEq, sort, prop, compose, head } = require('ramda');
const haveStock = propEq('stock', true);
const getHaveStock = filter(haveStock);
const sortByPriceDesc = sort((a, b) => b.price - a.price);
const getName = prop('name');

const getMostExpensiveFruitName = compose(
  getName, head, sortByPriceDesc, getHaveStock
);

console.log(getMostExpensiveFruitName(fruits)); // Peach
複製代碼

上述代碼的優勢體如今如下三方面:

  1. 代碼從可讀性上來講提高了
  2. 代碼可複用性加強了,haveStock、getHaveStock、sortByPriceDesc、getName這些函數並無關注被處理的數據,而是關注處理自己。
  3. 純函數利於寫單元測試

備註:ramda其實能夠大體理解爲函數式風格的lodash工具庫,它和lodash的區別主要在於兩點:

  1. Ramda函數自己都是自動柯里化的。
  2. Ramda函數參數的排列順序更便於柯里化。要操做的數據一般在最後面。(也意味着一般第一個傳入的參數是函數方法)。

小結

Pointfree風格的確須要必定的時間才能習慣,可是也不能一律而論把全部函數的參數都移除掉,具體狀況仍是須要具體分析。Pointfree風格雖然有時候也會形成一些困惑,但的確讓代碼更加簡潔和易於理解了。

固然,若是願意花點時間去練習和習慣Pointfree風格,相信仍是會很值得的。

相關文章
相關標籤/搜索