为什么Folktale和Ramda如此不同?

102

我正在通过阅读DrBoolean的书籍来学习JavaScript函数式编程。

我搜索了一些函数式编程库,找到了Ramda和Folktale。它们都声称自己是函数式编程库。

但它们非常不同:

  • Ramda似乎包含用于处理列表的实用程序函数:map、reduce、filter以及纯函数:curry、compose。它不包含任何与单子、函子有关的内容。

  • Folktale中没有任何与列表或函数相关的实用程序。它似乎在JavaScript中实现一些代数结构,如单子:Maybe、Task...

事实上,我发现了更多的库,它们似乎都属于这两个类别。Underscore和lodash与Ramda类似。Fantasy-land和pointfree-fantasy则类似于Folktale。

这些非常不同的库都可以被称为函数式吗?如果可以,每个库又有什么使其成为函数式库的特点呢?


1
使用适合您需求和风格的内容,并进行充分记录。 - charlietfl
1
我发现在JS中,“函数式编程”实际上有三种常见的意思。1. 在集合(如数组)上使用高阶纯函数,例如[1,2,3].map(fnSquare).reduce(fnSum)。2. 大多是学术性质的“看妈,没有_var_”Y组合结构。3. 使用Function.prototype来修改其他函数的行为,例如var isMissingID=fnContains.partial("id").negate(); - dandavis
11
Ramda 作者在此:Ramda 是一个相对较低级别的实用程序库。它旨在使特定的 JS 函数式风格更简单,尤其是通过组合函数来工作。Ramda 与 FantasyLand 规范非常配合,包括 Folktale 等实现。这些库的设计目的略有不同,它们围绕识别常见的抽象数据类型并允许一致地访问它们构建:例如 Monoid、Functor 和 Monad 等。Ramda 可以与它们一起使用,并有一个附属项目用于创建一些库,但正如您所说,这是一个非常不同的重点。 - Scott Sauyet
1
@KeithNicholas:是的,有一个Gitter Room - Scott Sauyet
2
也可以看一下 Sanctuary - https://github.com/plaid/sanctuary,这个库基于 Ramda,同时还包含了 Fantasy Land 类型。 - arcseldon
显示剩余4条评论
1个回答

189

功能特点

定义函数式编程或函数式库的界限并不明确。一些函数式语言的特性已内置到Javascript中:

  • 一等高阶函数
  • 带有闭包的lambda/匿名函数

其他特性在Javascript中也可以通过一些小心处理来实现:

  • 不可变性
  • 引用透明性

另外,一些特性已经是ES6的一部分,并且部分或完全可以直接使用:

  • 精简、甚至简洁的函数
  • 通过尾调用优化实现高效的递归

还有许多其他特性是真正超出了Javascript的正常范围:

  • 模式匹配
  • 惰性求值
  • Homoiconicity

因此,一个库可以根据自己要支持的特性选择和挑选,并仍然合理地被称为“函数式的”。

Fantasy-land规范

Fantasy-land是一种规范,它将数学范畴论和抽象代数中的许多标准类型移植到函数式编程中,例如MonoidFunctorMonad。这些类型相当抽象,并扩展了可能更为熟悉的概念。例如,Functor是可以使用函数进行映射的容器,就像数组可以使用Array.prototype.map进行映射一样。

Folktale

Folktale是一个集合,其中包含实现Fantasy-land规范和一小部分伴随实用程序函数的各种类型。这些类型是像MaybeEitherTask(与其他地方称为Future非常相似,是Promise的更合法表兄弟)和Validation之类的东西。

Folktale是Fantasy-land规范中最著名的实现之一,备受推崇。但并不存在一个权威或默认的实现;fantasy-land仅指定了抽象类型,而实现当然必须创建这些具体类型。Folktale声称自己是一个函数式库:它提供通常在函数式编程语言中找到的数据类型,这些类型使得以函数式的方式编程变得更加容易。

以下示例来自于Folktale文档注意:不在最近版本的文档中),展示了它如何被使用:

// We load the library by "require"-ing it
var Maybe = require('data.maybe')

// Returns Maybe.Just(x) if some `x` passes the predicate test
// Otherwise returns Maybe.Nothing()
function find(predicate, xs) {
  return xs.reduce(function(result, x) {
    return result.orElse(function() {
      return predicate(x)?    Maybe.Just(x)
      :      /* otherwise */  Maybe.Nothing()
    })
  }, Maybe.Nothing())
}

var numbers = [1, 2, 3, 4, 5]

var anyGreaterThan2 = find(function(a) { return a > 2 }, numbers)
// => Maybe.Just(3)

var anyGreaterThan8 = find(function(a) { return a > 8 }, numbers)
// => Maybe.Nothing

Ramda

Ramda(免责声明:我是其中一位作者)是一个非常不同类型的库。它不会为您提供新的类型。1相反,它提供了操作现有类型更容易的函数。它构建在将较小的函数组合成较大函数,使用不可变数据和避免副作用的概念周围。

Ramda尤其适用于列表,但也适用于对象和有时字符串。它还以这样一种方式委托其许多调用,以便与Folktale或其他Fantasy-land实现进行交互。例如,Ramda的map函数类似于Array.prototype上的函数,因此R.map(square, [1, 2, 3, 4]); //=> [1, 4, 9, 16]。但由于Folktale的Maybe实现Fantasy-land Functor规范,该规范还指定了map,因此您也可以使用Ramda的map

R.map(square, Maybe.Just(5)); //=> Maybe.Just(25);
R.map(square, Maybe.Nothing); //=> Maybe.Nothing

Ramda的功能库声称它可以轻松地组合函数,不会改变您的数据,并仅呈现纯函数。 Ramda的典型用法是通过组合较小的函数来构建更复杂的函数,正如在Ramda哲学文章中所看到的。

// :: [Comment] -> [Number]  
var userRatingForComments = R.pipe(
    R.pluck('username')      // [Comment] -> [String]
    R.map(R.propOf(users)),  // [String] -> [User]
    R.pluck('rating'),       // [User] -> [Number]
);

其他库

实际上,我发现了更多的库,它们似乎都属于两种类别。underscore、lodash很像Ramda。Fantasy-land、pointfree-fantasy类似于folktale。

那并不准确。首先,Fantasy-land只是一种规范,可以由各种类型的库决定是否实现。Folktale是该规范的许多实现之一,可能是最全面的一个,也是最成熟的一个之一。Pointfree-fantasyramda-fantasy是其他一些实现,并且还有许多其他的实现

Underscorelodash外表看起来像Ramda,因为它们是杂货袋式的库,提供大量函数,比Folktale等东西的凝聚度要低得多,而且即使特定的功能通常也会与Ramda的重叠。但在更深的层面上,Ramda与那些库有非常不同的关注点。Ramda的最亲密表兄弟可能是像FKitFnucWu.js之类的库。

Bilby处于自己的类别中,提供了一些工具,例如Ramda提供的工具,以及与Fantasy-land一致的一些类型。(Bilby的作者也是Fantasy-land的原作者。)

你的选择

所有这些库都有权被称为函数式,尽管它们在函数式方法和程度上差异很大。

其中一些库实际上可以很好地配合使用。Ramda应该能够很好地与Folktale或其他Fantasy-land实现配合使用。由于它们的关注点几乎没有重叠,因此它们实际上并不冲突,但是Ramda做了足够多的工作,使得它们的互操作相对平滑。对于您可能选择的其他组合,这可能不那么真实,但ES6的简单函数语法也可以减少集成的一些痛苦。

选择库,甚至选择使用的库的风格,将取决于您的项目和偏好。有许多好的选择可用,数量正在增加,并且它们中的许多正在大大改进。现在是在JS中进行函数式编程的好时机。


1嗯,有一个名为ramda-fantasy的副项目正在做类似于Folktale的事情,但它不是核心库的一部分。


1
ES6 引入 Yield 后,是否可以说它具备了惰性求值的能力? - Marcel Lamothe
8
yield 可以方便实现像 Lazy 或者 lz.js 这样的惰性列表处理,但它并不能帮助实现语言级别的惰性计算。在 JavaScript 中,someFunc(a + b) 首先会把 ab 的值相加,然后将其作为参数传递给 someFunc。在 Haskell 中对应的操作不会立即执行加法运算。如果被调用的函数从未使用该值,则永远不会执行加法运算。如果最终需要使用该值,则将其视为表达式直到需要其结果为止。除非强制执行(例如 IO),否则它永远不会真正执行计算。 - Scott Sauyet
@ScottSauyet 或许你会争辩说ES6生成器提供了某种形式的“惰性求值” - 很多JS框架具有“惰性”的特性 - RxJs、ImmutableJs等。 - arcseldon
与 Haskell 的语言级别惰性相比,这一点可以理解。 - arcseldon
1
Ramda文档倾向于使用“列表”作为JS提供的最接近列表的东西的简写,即密集数组。它们具有不同的性能特征和略微不同的API,但它们可以用于大部分相同的目的,并且它们是最接近原生(但请参见https://github.com/funkia/list)类型可用于Ramda的API,这个API在概念上想要使用纯列表。我认为不真实的数组点,因为C样式的数组不比JS样式的数组更加规范,也没有一个真正接近数学上的数组,但这只是一个小问题。 - Scott Sauyet
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接