函数式编程中的pointfree的意思就是“无参”或“无值”,pointfree style是一种编程范式,也作tacit programming,就是“无参编程”的意思了。什么是“无参编程”?
// 这就是有参的,因为有word
var snakeCase = word => word.toLowerCase().replace(/\s+/ig, '_');
// 这是pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);
从另一个角度看,有参的函数的目的是得到一个数据,而pointfree的函数的目的是得到另一个函数。
所以,如下的方程,虽然也有参,也可以认为是pointfree的。
const titlesForYear = year =>
pipe(
filter(publishedInYear(year)),
map(book => book.title)
)
那这pointfree有什么用?
它可以让我们把注意力集中在函数上,参数命名的麻烦肯定是省了,代码也更简洁优雅。
需要注意的是,一个pointfree的函数可能是由众多非pointfree的函数组成的,也就是说底层的基础函数大都是有参的,pointfree体现在用基础函数组合而成的高级函数上。如果我们使用函数式编程的工具,如ramda,这些基础函数大都已经被写好了,这样我们去写pointfree的代码就很容易了。
什么是声明式编程?它区别于命令式编程
// 命令式
var words = [];
for (i = 0; i < otherWords.length; i++) {
words.push(otherWords[i].word);
}
// 声明式
var words = otherWords.map(function(ele){ return ele.word; });
容易看出,命令式的代码,我们不但要去遍历,还要关注如何遍历。而声明式的就容易很多,可以节省我们的注意力,代码也更加简洁。
其他的命令式的写法有:使用ifelse
进行的条件判断,使用算数运算符进行的算数运算,使用比较运算符进行的比较运算和使用逻辑运算符进行的逻辑运算。
至于那些说“虽然如此,但使用命令式循环速度要快很多”的人,我建议你们先去学学 JIT 优化代码的相关知识。这里有一个 非常棒的视频,可能会对你有帮助。
需要注意的是,要实现这种声明式的编程,首先我们要有这个map
方法,这一点与pointfree相同,都是需要我们先对常用的操作做一次封装,而这些常用的操作本身还是命令式的。
pointfree的声明式代码是函数式编程应该有的样子。
最后用一个来自Scott Sauyet的文章《Favoring Curry》中的例子,使用的函数式工具是ramda。下面的代码不需要一句一句的看,大概体会一下就可以了。
一组JSON数据
var data = {
result: "SUCCESS",
interfaceVersion: "1.0.3",
requested: "10/17/2013 15:31:20",
lastUpdated: "10/16/2013 10:52:39",
tasks: [
{id: 104, complete: false, priority: "high",
dueDate: "2013-11-29", username: "Scott",
title: "Do something", created: "9/22/2013"},
{id: 105, complete: false, priority: "medium",
dueDate: "2013-11-22", username: "Lena",
title: "Do something else", created: "9/22/2013"},
{id: 107, complete: true, priority: "high",
dueDate: "2013-11-22", username: "Mike",
title: "Fix the foo", created: "9/22/2013"},
{id: 108, complete: false, priority: "low",
dueDate: "2013-11-15", username: "Punam",
title: "Adjust the bar", created: "9/25/2013"},
{id: 110, complete: false, priority: "medium",
dueDate: "2013-11-15", username: "Scott",
title: "Rename everything", created: "10/2/2013"},
{id: 112, complete: true, priority: "high",
dueDate: "2013-11-27", username: "Lena",
title: "Alter all quuxes", created: "10/5/2013"}
// , ...
]
};
需求是找到Scott所有未完成的任务,并按照到期日期升序排列。
正确的结果是
[
{id: 110, title: "Rename everything",
dueDate: "2013-11-15", priority: "medium"},
{id: 104, title: "Do something",
dueDate: "2013-11-29", priority: "high"}
]
命令式的代码如下
getIncompleteTaskSummaries = function(membername) {
return fetchData()
.then(function(data) {
return data.tasks;
})
.then(function(tasks) {
var results = [];
for (var i = 0, len = tasks.length; i < len; i++) {
if (tasks[i].username == membername) {
results.push(tasks[i]);
}
}
return results;
})
.then(function(tasks) {
var results = [];
for (var i = 0, len = tasks.length; i < len; i++) {
if (!tasks[i].complete) {
results.push(tasks[i]);
}
}
return results;
})
.then(function(tasks) {
var results = [], task;
for (var i = 0, len = tasks.length; i < len; i++) {
task = tasks[i];
results.push({
id: task.id,
dueDate: task.dueDate,
title: task.title,
priority: task.priority
})
}
return results;
})
.then(function(tasks) {
tasks.sort(function(first, second) {
var a = first.dueDate, b = second.dueDate;
return a < b ? -1 : a > b ? 1 : 0;
});
return tasks;
});
};
pointfree的代码
var getIncompleteTaskSummaries = function(membername) {
return fetchData()
.then(R.prop('tasks'))
.then(R.filter(R.propEq('username', membername)))
.then(R.reject(R.propEq('complete', true)))
.then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
.then(R.sortBy(R.prop('dueDate')));
};
pointfree的声明式的代码
// 提取 tasks 属性
var SelectTasks = R.prop('tasks');
// 过滤出指定的用户
var filterMember = member => R.filter(
R.propEq('username', member)
);
// 排除已经完成的任务
var excludeCompletedTasks = R.reject(R.propEq('complete', true));
// 选取指定属性
var selectFields = R.map(
R.pick(['id', 'dueDate', 'title', 'priority'])
);
// 按照到期日期排序
var sortByDueDate = R.sortBy(R.prop('dueDate'));
// 合成函数
var getIncompleteTaskSummaries = function(membername) {
return fetchData().then(
R.pipe(
SelectTasks,
filterMember(membername),
excludeCompletedTasks,
selectFields,
sortByDueDate,
)
);
};
参考文章
- Pointfree编程风格指南
- Favoring Curry
- JS函数式编程指南
- Tacit programming
- Thinking in Ramda: Pointfree Style
- Thinking in Ramda: Declarative Programming
我在github https://github.com/zhuanyongx...