从functional-programming-jargon开始:函数式编程新手入门必备词汇
你是否也曾在学习函数式编程时被各种专业术语弄得晕头转向?像"纯函数"、"柯里化"、"函子"这些词汇听起来高深莫测,却又无处不在。别担心,本文将带你从readme.md出发,用通俗易懂的方式解释这些核心概念,帮你轻松迈入函数式编程的大门。读完本文,你将能够清晰理解并运用函数式编程中的关键术语,告别术语困惑,提升代码质量。
为什么需要这份词汇表
函数式编程(Functional Programming, FP)凭借其诸多优势,近年来越来越受欢迎。然而,每种编程范式都有其独特的术语,函数式编程也不例外。通过这份词汇表,我们希望能让学习FP变得更加容易。本项目中的示例均以JavaScript(ES2015)呈现,你可以在readme.md中找到更多细节。
基础核心概念
函数元数(Arity)
函数元数指的是一个函数所接受的参数数量。来自于像一元、二元、三元这样的词汇。
const sum = (a, b) => a + b
// sum的元数是2(二元)
const inc = a => a + 1
// inc的元数是1(一元)
const zero = () => 0
// zero的元数是0(零元)
高阶函数(Higher-Order Functions, HOF)
高阶函数是指可以接受函数作为参数,并且/或者返回一个函数的函数。
const filter = (predicate, xs) => xs.filter(predicate)
const is = (type) => (x) => Object(x) instanceof type
filter(is(Number), [0, '1', 2, null]) // [0, 2]
闭包(Closure)
闭包是一个可以捕获函数内部局部变量并在函数执行完毕后仍然能够访问这些变量的作用域。这使得闭包中的值可以被返回的函数访问。
const addTo = x => y => x + y
const addToFive = addTo(5)
addToFive(3) // => 8
在这个例子中,x的值5被保留在addToFive的闭包中。然后可以调用addToFive并传入y来得到它们的和。
函数转换与组合
偏应用(Partial Application)
偏应用一个函数意味着通过预先填充原始函数的一些参数来创建一个新函数。
// 创建偏应用函数的辅助工具
// 接受一个函数和一些参数
const partial = (f, ...args) =>
// 返回一个接受剩余参数的函数
(...moreArgs) =>
// 并使用所有参数调用原始函数
f(...args, ...moreArgs)
// 要应用的函数
const add3 = (a, b, c) => a + b + c
// 将2和3部分应用于add3,得到一个单参数函数
const fivePlus = partial(add3, 2, 3) // (c) => 2 + 3 + c
fivePlus(4) // 9
你也可以使用Function.prototype.bind在JS中偏应用一个函数:
const add1More = add3.bind(null, 2, 3) // (c) => 2 + 3 + c
偏应用通过在已有数据时预先填充参数,帮助从更复杂的函数创建更简单的函数。柯里化函数会自动进行偏应用。
柯里化(Currying)
柯里化是将一个接受多个参数的函数转换为一系列只接受一个参数的函数的过程。
每次调用函数时,它只接受一个参数,并返回一个接受下一个参数的函数,直到所有参数都被传递。
const sum = (a, b) => a + b
const curriedSum = (a) => (b) => a + b
curriedSum(40)(2) // 42.
const add2 = curriedSum(2) // (b) => 2 + b
add2(10) // 12
函数组合(Function Composition)
函数组合是将两个函数组合在一起形成第三个函数的行为,其中一个函数的输出是另一个函数的输入。这是函数式编程中最重要的思想之一。
const compose = (f, g) => (a) => f(g(a)) // 定义
const floorAndToString = compose((val) => val.toString(), Math.floor) // 使用
floorAndToString(121.212121) // '121'
函数式编程的核心原则
纯函数(Pure Function)
如果一个函数的返回值仅由其输入值决定,并且不产生副作用,那么这个函数就是纯函数。当给定相同的输入时,函数必须始终返回相同的结果。
const greet = (name) => `Hi, ${name}`
greet('Brianne') // 'Hi, Brianne'
与之相对的是以下每个例子:
window.name = 'Brianne'
const greet = () => `Hi, ${window.name}`
greet() // "Hi, Brianne"
上面例子的输出基于函数外部存储的数据...
let greeting
const greet = (name) => {
greeting = `Hi, ${name}`
}
greet('Brianne')
greeting // "Hi, Brianne"
...而这个例子修改了函数外部的状态。
副作用(Side effects)
如果一个函数或表达式除了返回值之外,还与外部可变状态进行交互(读取或写入),那么就说它有副作用。
const differentEveryTime = new Date()
console.log('IO is a side effect!')
常见的数据类型与结构
函子(Functor)
函子是实现了map函数的对象,map函数接受一个函数并作用于该对象的内容。函子必须遵守两条规则:
保持恒等性
object.map(x => x)
等价于object本身。
可组合性
object.map(x => g(f(x)))
等价于
object.map(f).map(g)
(f,g是任意可组合函数)
Option的参考实现是一个函子,因为它满足这些规则:
Some(1).map(x => x) // = Some(1)
和
const f = x => x + 1
const g = x => x * 2
Some(1).map(x => g(f(x))) // = Some(4)
Some(1).map(f).map(g) // = Some(4)
单子(Monad)
单子是具有of和chain函数的对象。chain类似于map,但它会将结果中的嵌套对象展开。
// 实现
Array.prototype.chain = function (f) {
return this.reduce((acc, it) => acc.concat(f(it)), [])
}
// 使用
Array.of('cat,dog', 'fish,bird').chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird']
// 与map对比
Array.of('cat,dog', 'fish,bird').map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']]
of在其他函数式语言中也称为return。 chain在其他语言中也称为flatmap和bind。
幺半群(Monoid)
幺半群是具有"组合"函数的对象,该函数将该对象与另一个相同类型的对象组合(半群),并且具有"单位元"值。
一个简单的幺半群是数字的加法:
1 + 1 // 2
在这种情况下,数字是对象,+是函数。
当任何值与"单位元"值组合时,结果必须是原始值。单位元还必须是可交换的。
加法的单位元是0。
1 + 0 // 1
0 + 1 // 1
1 + 0 === 0 + 1
还要求运算的分组不会影响结果(结合律):
1 + (2 + 3) === (1 + 2) + 3 // true
数组连接也形成幺半群:
[1, 2].concat([3, 4]) // [1, 2, 3, 4]
单位元是空数组[]:
[1, 2].concat([]) // [1, 2]
作为反例,减法不形成幺半群,因为没有可交换的单位元:
0 - 4 === 4 - 0 // false
如何进一步学习
函数式编程是一个丰富而深入的领域,本文介绍的只是冰山一角。要深入学习,建议参考readme.md中的"Further reading"部分,那里提供了更多优质资源。同时,你也可以尝试使用函数式编程库,如Lodash或Ramda,在实践中巩固这些概念。
希望这份词汇表能帮助你更好地理解函数式编程。记住,掌握这些术语只是第一步,真正的理解来自于不断的实践和应用。祝你在函数式编程的学习道路上越走越远!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



