何时在函数式编程中选择无点样式与以数据为中心的样式是否合适?

cus*_*der 3 javascript functional-programming pointfree ramda.js

如果重要的话,这是关于JavaScript中的函数式编程,在我的例子中,我将使用Ramda.

虽然工作中的每个人都完全接受了函数式编程,但还有很多关于如何"正确"地进行讨论的讨论.

这两个函数将完全相同:获取一个列表并返回一个新列表,其中所有字符串都已被修剪.

// data-centric style
const trimList = list => R.map(R.trim, list);
Run Code Online (Sandbox Code Playgroud)
// point-free style
const trimList = R.map(R.trim);
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.然而,对于更复杂的示例,两种样式之间的差异是惊人的:获取列表并返回一个新列表,其中所有字符串都等于在对象中找到的属性.

var opts = {a: 'foo', b: 'bar', c: 'baz'}; 
var list = ['foo', 'foo', 'bar', 'foo', 'baz', 'bar'];

myFilter(opts, 'a', list); //=> ["foo", "foo", "foo"]
myFilter(opts, 'b', list); //=> ["bar", "bar"]
Run Code Online (Sandbox Code Playgroud)
// data-centric style
const myFilter = (opts, key, list) => {
  var predicate = R.equals(opts[key]);
  return R.filter(predicate, list);
};
Run Code Online (Sandbox Code Playgroud)
// point-free style
const myFilter = R.converge(
  R.filter, [
    R.converge(
      R.compose(R.equals, R.prop), [
        R.nthArg(1),
        R.nthArg(0)]),
    R.nthArg(2)]);
Run Code Online (Sandbox Code Playgroud)

除了可读性和个人品味外,是否还有可靠的证据表明在某些情况下一种风格比另一种更适合?

Sco*_*yet 6

我知道没有证据表明一种风格优于另一种风格。但是在编程的历史上有一个明显的趋势是朝着更高的抽象发展……并且同样明显地反对这种趋势。从汇编到 Fortran 或 LISP 的转变是在抽象堆栈上的提升。使用 SQL 而不是定制的 B 树检索是另一种。向 FP 的转变,无论是在 Javascript 这样的语言中还是在不断变化的编程语言环境中,在我看来都是类似的举动。

但其中大部分与比这个句法决定更基本的元素有关:等式推理意味着我们可以在更坚实的基础上构建我们自己的抽象。所以纯度和不变性是必不可少的;点免费只是很好。

也就是说,它通常更简单。这很重要。更简单的代码更容易阅读,更容易修改。请注意,我区分了simpleeasyRich Hickey的经典演讲中阐明的区别。那些刚接触这种风格的人往往会发现它更令人困惑;憎恶下一代语言及其同类的汇编程序员也是如此。

通过不定义中间变量,甚至不指定可以推断的参数,我们可以显着提高简单性。

很难说:

const foo = (arg) => {
  const qux = baz(arg)
  return bar(qux)
}
Run Code Online (Sandbox Code Playgroud)

甚至这个:

const foo = (arg) => bar(baz(arg))
Run Code Online (Sandbox Code Playgroud)

比这更简单:

const foo = compose(bar, baz)
Run Code Online (Sandbox Code Playgroud)

那是因为虽然所有三个都涉及这些概念:

  • 函数声明
  • 函数参考

第二个还补充说:

  • 参数定义
  • 函数体
  • 功能应用
  • 函数调用的嵌套

第一个版本有:

  • 参数定义
  • 函数体
  • 功能应用
  • 局部变量定义
  • 局部变量赋值
  • return声明

而第三个只添加

  • 功能组合

如果更简单意味着纠缠的概念更少,那么无点版本更简单,即使它对某些人来说不太熟悉。


最后,其中大部分归结为可读性。与编写代码相比,您花在阅读自己代码上的时间更多。任何人都花了很多更多的时间阅读。如果您编写的代码简单易读,那么每个人的体验都会变得更好。因此,在无点代码更具可读性的地方,使用它。

但是不要觉得有必要删除每一点,以证明一个观点。很容易陷入仅仅因为你可以而试图让一切都变得毫无意义的陷阱。我们已经知道这是可能的;我们不需要看到血腥的细节。

  • @reify:我要表达的观点——以及 Rich Hickey 演讲的主旨——是 *轻松* / *熟悉* 实际上是相对衡量标准;*简单*实际上更客观。 (2认同)

小智 5

学术术语是eta转换.当你有一个带冗余lambda抽象的函数时

const trim = s => s.trim();
const map = f => xs => xs.map(x => f(x));

const trimList = xs => map(trim) (xs); // lambda redundancy
Run Code Online (Sandbox Code Playgroud)

你可以通过eta减少简单地删除最后的lamdba抽象:

const trimList = map(trim);
Run Code Online (Sandbox Code Playgroud)

当你广泛使用eta减少时,你最终会得到无点风格.但是,这两个版本在功能范例中都非常精细.这只是一种风格问题.

实际上,在Javascript中使用eta抽象(与eta减少相反)至少有两个原因:

  • 像我一样修复Javascript的多参数函数 map = f => xs => xs.map(x => f(x))
  • 防止立即评估表达式/语句(延迟评估效果),如 recur = f => x => f(recur(f)) (x)