如何在JavaScript中使用pointfree样式而不会失去可读性?

时间:2017-02-06 12:52:49

标签: javascript functional-programming ramda.js pointfree

当我尝试以无点样式编写JavaScript时,我发现如果你强制使用这种风格的每一个函数,你有时会失去它的可读性。例如:

import R from 'ramda'

const ceil = Math.ceil

const pagination = {
  total: 101,
  itemsPerPage: 10,
  currentPage: 1
}

// ================= Pointful style ==================
const pageCount = (pagination) => {
  const pages = ceil(pagination.total / pagination.itemsPerPage)
  const remainPages = pagination.total % pagination.itemsPerPage === 0 ? 0 : 1
  return pages + remainPages
} 

pageCount(pagination) // => 11

// ================ Pointfree style ==================
const getPages = R.pipe(
  R.converge(R.divide, [R.prop('total'), R.prop('itemsPerPage')]),
  ceil
)

const getRemainPages = R.ifElse(
  R.pipe(
     R.converge(R.modulo, [R.prop('total'), R.prop('itemsPerPage')]),
     R.equals(0)
  ),
  R.always(0),
  R.always(1)
)

const pageCount2 = R.converge(R.add, [
  getPages,
  getRemainPages
])

pageCount2(pagination) // => 11

我编写了一个简单的分页模块来计算pageCount,以有点样式和无点样式计算每页的总项数和项目数。显然,有点样式比无点样式版本更具可读性。后者有点模糊。

我做得对吗?有没有办法让dotfree样式的代码更具可读性?

3 个答案:

答案 0 :(得分:8)

手动构图

让我们从手动编写功能开始:



const calcPages = (totalItems, itemsPerPage) =>
 ceil(div(totalItems, itemsPerPage));


const div = (x, y) => x / y;

const ceil = Math.ceil;


const pagination = {
  total: 101,
  itemsPerPage: 10,
  currentPage: 1
}


console.log(
  calcPages(pagination.total, pagination.itemsPerPage)
);




程序化作文

下一步我们抽象出参数:



const comp2 = (f, g) => (x, y) => f(g(x, y));

const div = (x, y) => x / y;

const ceil = Math.ceil;


const calcPages = comp2(ceil, div);


const pagination = {
  total: 101,
  itemsPerPage: 10,
  currentPage: 1
}


console.log(
  calcPages(pagination.total, pagination.itemsPerPage)
);




函数定义现在是无点的。但是调用代码不是。如果你知道高阶函数comp2是如何工作的,那么表达式comp2(ceil, div)对你来说是很有说服力的。

现在显而易见的是,calcPages是错误的名称,因为函数组合更为通用。让我们称之为...... intDiv(好吧,可能有一个更好的名字,但我很擅长数学)。

解构修饰符

在下一步中,我们修改intDiv以便它可以处理对象:



const destruct2 = (x, y) => f => ({[x]:a, [y]:b}) => f(a, b);

const comp2 = (f, g) => (x, y) => f(g(x, y));

const div = (x, y) => x / y;

const ceil = Math.ceil;


const intDiv = comp2(ceil, div);

const calcPages = destruct2("total", "itemsPerPage") (intDiv);


const pagination = {
  total: 101,
  itemsPerPage: 10,
  currentPage: 1
}


console.log(
  calcPages(pagination)
);




我再次调用了修改后的函数calcPages,因为它现在需要一个特定的pagination对象,因此不太通用。

如果你知道所涉及的高阶函数是如何工作的,那么一切都是声明性的并且可读性很好,即使它是以无点样式编写的。

结论

无点样式是函数组合,currying和高阶函数的结果。这本身并不是一件事。如果您停止使用这些工具以避免无点样式,那么您将失去许多功能性编程所提供的表现力和优雅。

答案 1 :(得分:4)

让我们从一个简单的例子开始:

//    inc :: Number -> Number
const inc = R.add(1);

我发现上面的内容比其有点"更清晰。当量:

//    inc :: Number -> Number
const inc = n => R.add(1)(n);

上面一行中的lambda只是一个人熟悉部分应用Ramda函数的噪音。

让我们走到另一个极端:

//    y :: Number
const y = R.ifElse(R.lt(R.__, 0), R.always(0), Math.sqrt)(x);

这将很多更明确地写在"有点"式:

//    y :: Number
const y = x < 0 ? 0 : Math.sqrt(x);

我的建议是在简单的情况下使用无点表达式,并恢复为&#34;有点&#34;表达式变得复杂时的样式。我经常走得太远,然后撤消我最后几次更改,以回到更清晰的表达。

答案 2 :(得分:1)

在我看来,这里的问题是“可读性”,更具体地说,事实上,你不能从书本上从左到右连续阅读代码。

ramda库中的某些函数可以从右到左阅读,如compose()

import { compose, add, multiply } from 'ramda'

// the flow of the function is from right to left
const fn = compose(add(10), multiply) // (a, b) => (a * b) + 10

然后你会到达某一点,某些函数要求你以一定的顺序(非交换函数)给出它们的参数,通常是从左到右的方式,如lt()

import { __, lt } from 'ramda'

// you need to read the parameters from left to right to understand it's meaning
const isNegative = lt(__, 0) // n => n < 0

当这些方向变化满足时,您将更难以阅读代码,因为需要花费更多精力才能找到代码流动的路径。

import { compose, length, gte, __ } from 'ramda'

// 1) you start reading the code from left to right
// 2) you see compose: switch modes to right to left, jump to the end of compose
// 3) you get to gte, where you need to switch back to left to right mode
// 4) glue all previous parts together in your mind, hope you'll still remember where you started
const hasAtLeast3Items = compose(gte(__, 3), length) // arr => arr.length >= 3

在使用ramda时,有一些功能非常有用,但可能会立即破坏可读性。这些要求您多次切换阅读方向,要求您跟踪代码中的太多子步骤。我从该列表中获得的第一个功能是converge

注意:上述问题似乎只发生在具有多于1个参数的函数上,但add()没有此问题,add(__, 3)add(3)是相同的