在之前的例子中,我们遍历了数组两次,一次用来获取users,一次为了获取names。如果能在一次map映射操作中同时做这两件事情,效率会高很多。
function friendsNames(usersById, user) {
return user.friendIds.map(function(id) {
var friend = lookup(usersById, id)
return lookup(friend, "name")
})
}
我们得到首次lookup
的结果,把它第二次传入lookup
。函数组合意思是串联多个函数,组成一个新的函数,每一次串联都是把前一个函数的输出当作下一个函数的输入。
让我们来写一个能这样运转的高阶函数,利用它把friendsNames
函数重写成一个只需要单次map操作的函数。需要注意的是,函数串联的执行顺序是从右到左的,就跟你写出f(g(x))
这样的代码的运行方式一样。
function compose(f, g) {
return function(x) {
return f(g(x))
}
}
function friendsNames(usersById, user) {
return user.friendIds.map(compose(applyr(lookup, "name"), apply(lookup, usersById)))
}
对数组的遍历只进行了一次,只使用一次map操作,跟我们头一个例子一样。
我们不能使用我们写出的friends
函数,因为它既包含了如何取出一个friend的业务逻辑,也包含了map操作。friends
函数是不能复用的,它的职责太多了——它是针对特定事物的。如果你们再写一个friend
函数,让它只map一个friend,写一个name
函数,让它返回对象的名称呢?
var friend = lookup // lookup 恰巧能干我们想要的事情。
var name = applyr(lookup, "name")
function friendsNames(usersById, user) {
// this line is now more semantic.
return user.friends.map(compose(name, apply(friend, usersById)))
}
相较于定义一个既包含转换操作,又包含遍历操作的friends
函数,我们只定义了一个可做转换操作的friend
函数,而我们已经有了map
函数为我做变换操作。friend
函数比friends
函数更具复用性,因为它包含更少的特定业务逻辑,能在更多的情形中使用。
在这里你能找到更多的关于JavaScript里函数组合的信息。
函数式和功能单一化让你的代码库更整洁
我发现我的很多的JavaScript代码都是从无到有自己写出来的。这不仅仅是说比起使用现成的程序包要效率低,它还会暗藏更多的bug,更难阅读和维护。使用高阶函数和偏函数用法,我们可以写出可复用的程序库,每个函数都精准的对应解决它们能解决的一部分问题。
随着时间的推移,项目会变得越来越复杂,各部分越来越耦合,如果我们拥有的是一个能够各自独立测试不依赖的程序库,我们的项目会从中受益,变得更健康,更稳定。
一种宽泛的组合。并不特指函数或对象组合,只是一种你用小东西组建大东西的思想。↩
“Matching functions”被称作predicates,但我这里不想引入新的编程术语。↩
这里有更通用的apply
实现。↩
[本文英文原文链接:Learn You a Haskell: For Great Good? ]