学习使用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风格,相信仍是会很值得的。

相关文章
相关标签/搜索