为什么使用Ramda(译)

栏目: 编程语言 · 发布时间: 5年前

内容简介:当Buzzdecafe(Ramda库的主要贡献者)最近将Ramda介绍给世界时,有两种截然不同的反应。那些习惯了函数式技术的人——不论是用JavaScript或其他语言——大多会回答“酷”。他们可能对此感到兴奋,或者只是随便地注意到另一个潜在的工具,但他们理解它的用途。第二种反应是:“哈?”对于那些不习惯于函数式编程的人来说,ramda似乎毫无用处。它的大部分主要功能已经被诸如Underscore和lodash之类的库所实现了。

当Buzzdecafe(Ramda库的主要贡献者)最近将Ramda介绍给世界时,有两种截然不同的反应。那些习惯了函数式技术的人——不论是用JavaScript或其他语言——大多会回答“酷”。他们可能对此感到兴奋,或者只是随便地注意到另一个潜在的工具,但他们理解它的用途。

第二种反应是:“哈?”

对于那些不习惯于函数式编程的人来说,ramda似乎毫无用处。它的大部分主要功能已经被诸如Underscore和lodash之类的库所实现了。

这些人是对的。如果你想用你一直使用的命令式和面向对象风格来编写代码,Ramda没有更多的功能提供给你。

然而,它确实提供了一种不同的编码风格,这种风格在纯函数式编程语言中被认为是理所当然的:Ramda通过函数组合的方式使构建复杂逻辑变得简单。请注意,任何具有compose函数的库都允许您进行函数组合;但Ramda真正的要点是:“使其简单化”。

Ramda是如何运作

让我们看看Ramda是如何运作的。

Web框架总是拿“todolist”当做事例,因此我们也用它做事例:想象这样一个场景,筛选todolist以删除所有已完成的项。

使用内置的数组原型方法,我们可以这样做:

// Plain JS
var incompleteTasks = tasks.filter(function(task) {
    return !task.complete;
});
复制代码

使用LoDash,会简单一点:

// Lo-Dash
var incompleteTasks = _.filter(tasks, {complete: false});

复制代码

上面两种情况,我们都会得到一个经过过滤的任务列表。

现在使用Ramda,我们可以这样做:

var incomplete = R.filter(R.where({complete: false});

复制代码

注意到什么东西不见了吗?任务列表tasks没有了。这个ramda代码只是给了我们一个函数。

为了得到结果,我们仍然需要用任务列表tasks来调用它。

var incompleteTasks = incomplete(tasks);
复制代码

这就是重点所在。

因为我们现在有了一个函数,我们可以很容易地将它与其他函数结合起来,然后再对数据进行操作。假设我们有一个函数groupbyuser,它按用户对todo项进行分组。然后我们可以简单地创建一个新的函数:

var activeByUser = R.compose(groupByUser, incomplete);
复制代码

上面代码实现了选择未完成的任务并按用户分组。

如果不使用Ramda的compose,而是自己手动实现函数组合,则需要写一个这样的函数:

// (if created by hand)
var activeByUser = function(tasks) {
    return groupByUser(incomplete(tasks));
};
复制代码

使用Ramda的好处就是不用每次手动实现函数组合。组合是函数式编程的关键技术之一。让我们多考虑一些情况。如果我们需要按截止日期对每个用户的todolist进行 排序 呢?

var sortUserTasks = R.compose(R.map(R.sortBy(R.prop("dueDate"))), activeByUser);
复制代码

把所有函数合并一个函数?

观察力强的读者可能已经注意到我们可以将上述所有内容合并起来。既然我们的compose函数允许两个以上的参数,为什么不在一个步骤中完成所有这些工作呢?

var sortUserTasks = R.compose(
    R.mapObj(R.sortBy(R.prop('dueDate'))),
    groupByUser,
    R.filter(R.where({complete: false})
);
复制代码

如果您没有其他地方调用函数activebyuser和incomplete,这样写可能是合理的。但是,它也会使调试变得更困难,并且不会增加代码的可读性。

事实上,我认为我们不应该把所有函数合并成一个函数。应该拆分可重用的部分。如果我们这样做,可能会更好:

var sortByDateDescend = R.compose(R.reverse, sortByDate);
var sortUserTasks = R.compose(R.mapObj(sortByDateDescend), activeByUser);
复制代码

如果我们确定我们只想先按最近的日期排序,那么我们可以只单独保留SortByDatedDescend函数。如果业务有按升序或降序对数据进行排序两种需求,应该保留sortByDate和sortByDateDescend函数都在,方便后续的组合。

数据哪里去了?

我们这回还没有处理数据。这是怎么回事?没有数据的数据处理只是过程。耐心写,当您使用函数式编程时,您所得到的只是组成管道的函数。一个函数向下一个函数提供数据,下一个函数向下下个函数提供数据,依此类推,直到需要的结果从末尾流出。

到目前为止,我们已经构建了以下函数:

incomplete: [Task] -> [Task]
sortByDate: [Task] -> [Task]
sortByDateDescend: [Task] -> [Task]
activeByUser: [Task] -> {String: [Task]}
sortUserTasks: {String: [Task]} -> {String: [Task]}
复制代码

我们已经使用前面的函数来构建sortUserTasks,也可以单独使用这些函数。这里面的activeByUser函数,其中的groupByUser函数,我还没有实现。我们要怎样编写它呢?

以下是groupByUser函数的实现:

var groupByUser = R.partition(R.prop('username'));
复制代码

再等等,看看Ramda还有更多方法

从任务列表中选择前五个元素,我们可以使用ramda的take函数,我们可以这样做:

var topFiveUserTasks = R.compose(R.mapObj(R.take(5)), sortUserTasks);
复制代码

我们只需要返回的对象中属性的子集,比如标题和截止日期。在这个数据结构中,用户名显然是多余的,我们不想传递给其他系统。

我们可以使用Ramda的类似于SQL select函数的方法来实现这一点,该函数被称为project:

var importantFields = R.project(['title', 'dueDate']);
var topDataAllUsers = R.compose(R.mapObj(importantFields), topFiveUserTasks);
复制代码

现在,我们的todolist应用程序中,可能有下面这些函数:

var incomplete = R.filter(R.where({complete: false}));
var sortByDate = R.sortBy(R.prop('dueDate'));
var sortByDateDescend = R.compose(R.reverse, sortByDate);
var importantFields = R.project(['title', 'dueDate']);
var groupByUser = R.partition(R.prop('username'));
var activeByUser = R.compose(groupByUser, incomplete);
var topDataAllUsers = R.compose(R.mapObj(R.compose(importantFields, 
    R.take(5), sortByDateDescend)), activeByUser);
复制代码

一直在说函数?数据呢?

现在是将数据传递到函数中的时候了。这些函数都接受相同类型的数据,即一个todo项数组。我们没有具体描述这些项目的结构,但我们知道它们必须至少具有以下属性:

complete: Boolean

dueDate: String, formatted YYYY-MM-DD

title: String

userName: String

所以,如果我们有一个任务数组,我们如何使用它?如下:

var results = topDataAllUsers(tasks);
复制代码

使用起来就是这么简单。 结果是一个对象,如下:

{
    Michael: [
        {dueDate: '2014-06-22', title: 'Integrate types with main code'},
        {dueDate: '2014-06-15', title: 'Finish algebraic types'},
        {dueDate: '2014-06-06', title: 'Types infrastucture'},
        {dueDate: '2014-05-24', title: 'Separating generators'},
        {dueDate: '2014-05-17', title: 'Add modulo function'}
    ],
    Richard: [
        {dueDate: '2014-06-22', title: 'API documentation'},
        {dueDate: '2014-06-15', title: 'Overview documentation'}
    ],
    Scott: [
        {dueDate: '2014-06-22', title: 'Complete build system'},
        {dueDate: '2014-06-15', title: 'Determine versioning scheme'},
        {dueDate: '2014-06-09', title: 'Add `mapObj`'},
        {dueDate: '2014-06-05', title: 'Fix `and`/`or`/`not`'},
        {dueDate: '2014-06-01', title: 'Fold algebra branch back in'}
    ]
}
复制代码

同样,我们也可以将任务数组传递给incomplete函数,得到一个筛选后的列表:

var incompleteTasks = incomplete(tasks);
复制代码

结果如下:

[
    {
        username: 'Scott',
        title: 'Add `mapObj`',
        dueDate: '2014-06-09',
        complete: false,
        effort: 'low',
        priority: 'medium'
    }, {
        username: 'Michael',
        title: 'Finish algebraic types',
        dueDate: '2014-06-15',
        complete: false,
        effort: 'high',
        priority: 'high'
    } /*, ... */
]
复制代码

当然,您也可以将任务数组传递给sortbydate、sortbydatedescend、importantfields、byuser或activebyuser。因为这些都在类似的类型上运行——任务数组——我们可以通过简单的组合构建一个大型的 工具 集合。

新需求

现在又有了一个新需求,我们的项目又要支持一个新特性,为特定用户筛选任务列表。拥有同上面相同的筛选,排序等功能。

var gloss = R.compose(importantFields, R.take(5), sortByDateDescend);
var topData = R.compose(gloss, incomplete);
var topDataAllUsers = R.compose(R.mapObj(gloss), activeByUser);
var byUser = R.use(R.filter).over(R.propEq("username"));
复制代码

下面是使用方式:

var results = topData(byUser('Scott', tasks));
复制代码

我不想使用函数合并,只操作数据,可以吗?

可以,如:

var incomplete = R.filter(R.where({complete: false}));
复制代码

我们不先得到复合函数,再操作,而是直接得到数据结果:

var incompleteTasks = R.filter(R.where({complete: false}), tasks);
复制代码

所有其他主要函数也是如此:只需在调用结束时添加一个tasks参数,就可以返回数据。

刚刚发生了什么?

Ramda的一个主要特性。就是所有函数都是自动柯里化的。这意味着,如果您没有提供函数期望的所有参数,将返回一个新的函数,此函数缓存了已经传递的参数,期望剩余的参数。上面的代码中,就是使用了柯里化这一特性,比如R.filter期待两个参数,我们只传递给它一个,那么它就返回一个新函数,期望再传递给新函数一个参数,才执行得到筛选出的最终数据。

自动柯里化特性,加上Ramda这种函数优先,数据最后的API设计风格,使Ramda非常适合编写函数式编程风格。

使用Ramda

node环境使用npm安装:

npm install ramda
var R = require('ramda')
复制代码

浏览器环境:

<script src="path/to/yourCopyOf/ramda.js"></script>
复制代码

<script src="path/to/yourCopyOf/ramda.min.js"></script>
复制代码

或使用一些CDN链接。

英文原文地址: fr.umio.us/why-ramda/


以上所述就是小编给大家介绍的《为什么使用Ramda(译)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

跨平台桌面应用开发:基于Electron与NW.js

跨平台桌面应用开发:基于Electron与NW.js

【丹】Paul B. Jensen / Goddy Zhao / 2018-3 / 99

《跨平台桌面应用开发:基于Electron与NW.js》是一本同时介绍 Electron和 NW.js的图书,这两者是目前流行的支持使用 HTML、CSS 和 JavaScript 进行桌面应用开发的框架。书中包含大量的编码示例,而且每个示例都是五脏俱全的实用应用,作者对示例中的关键代码都做了非常详细的解释和说明,可让读者通过实际的编码体会使用这两款框架开发桌面应用的切实感受。除此之外,在内容上,......一起来看看 《跨平台桌面应用开发:基于Electron与NW.js》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具