使用函数式编程的几个示例
函数式编程简介
函数式编程是指为创建不可变的程序,通过消除外部可见的副作用,来对纯函数的声明式的求值过程。
函数式编程具有引用透明、存储不可变数据、无副作用的特点。其属于声明式编程范式,关注的是如何用各种表达式来控制程序逻辑。
利用函数链进行序列化操作
链式操作既能减少临时变量声明,也能提高代码的可读性。
考虑一个场景,需要对一组姓名进行读取、规范化、去重,最终进行排序
输入: ['alonzo church', 'Haskell curry', 'stephen_kleene', 'John Von Neumann', 'stephen_kleene']
输出:['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']
示例代码如下所示:
arr
.filter(Boolean) // 过滤掉非法值,如null, undefined, ""等
.map(s => s.replace(/_/g, ' ').replace(/\b(\w)/g, p1 => p1.toUpperCase())) // 规范化值
.filter((it, ix, arr) => arr.indexOf(it) === ix) // 去重
.sort() // 排序
使用链式调用,整个过程清晰明了,代码可读性较高。在这过程中,没有声明任何临时变量,没有产生任何的冗余代码。
柯里化的函数求值
柯里化是一种在所有参数被提供之前,挂起或延迟函数执行,将多参函数转换为一元函数序列的技术,定义为:
curry(f) :: (a,b,c) -> f(a) -> f(b) -> f(c)
以一个经典的实现不定参数的求和函数为例:
// 实现不定参数的柯里化
const curry = (fn, ...args) => {
const f = (...nextArgs) => curry(fn, ...args, ...nextArgs)
f.getValue = () => fn.apply(null, args)
return f
}
// 定义累加函数
const sum = (...args) => args.reduce((total, item) => total + Number(item))
const add = curry(sum)
add(1, 2)(3)(4)()(6, 7, 8)(null, 0.2).getValue()
函数组合、管道函数
函数组合是将若干个函数组合成一个新的函数,同时完成数据的传递。函数组合能将函数的描述与求值分离开。
对于g :: A -> B
和 f :: B -> C
,则f.g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)
函数的管道化指的是以函数作为组件,将函数的输入和输出松散的连接在一起。即:
A -> f(A) -> B -> f(B) -> C
示例代码如下所示:
// 组合函数实现
const compose = (...fns) => args => fns.reduceRight((sum, fn) => fn(sum), args)
const normalize = str => str.split("-");
const dumplicate = arr => arr.filter((it, ix, array) => array.indexOf(it) === ix)
const findBiggest = arr => arr.sort((a, b) => b - a)[0]
const someFn = compose(findBiggest, dumplicate, normalize)
someFn("1-8-1-1-3-2-8-9-5-6-3")
惰性求值
惰性求值可避免不必要的函数调用,惰性求值的目的是尽可能地推迟求值,直到依赖的表达式被调用。可通过Generator
模拟惰性求值,通过next
操作下一步的值, next :: void -> { value, done }
,相关库可参考lazy.js。
// 简单实现一个惰性求值
const numbers = function* () {
let i = 1
while (true) {
yield i++
}
}
const Lazy = iterator => {
const next = iterator.next.bind(iterator)
const map = f => Lazy({
...iterator, next: () => {
const wrappedNext = next();
return { value: f(wrappedNext.value), done: wrappedNext.done }
}
})
const filter = f => Lazy({
...iterator, next: () => {
while (true) {
const wrappedNext = next();
if (f(wrappedNext.value)) return wrappedNext
}
}
})
const take = len => {
const result = [];
while (result.length < len) {
result.push(next().value)
}
return result;
}
return { next, map, filter, take }
}
Lazy(numbers()).map(x => x ** 2).filter(x => x % 2 === 1).take(10)
// 结果:[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
// 运行次数:map 19次,filter 19次
尾部调用优化
尾部调用优化(TCO),指的是在尾部位置调用函数。其会使用新帧来替换当前帧,而不是将新的帧叠加在旧帧之上,从而优化函数调用栈。
示例:
// 以常见的阶乘来演示尾递归
// 常规方式
const factorail = n => n <= 1 ? 1 : n * factorail(n - 1)
// 尾递归
const factorail = (n, cur = 1) => n <= 1 ? cur : factorail(n - 1, n * cur)
关于我
本文到此结束,感谢您的阅读,我们下篇文章见。
关注前端快爆
获取更多精彩
前端快爆
点下"在看",你最好看