函数式编程,从名称上看就与函数紧密相关。它是一种我们常常使用却可能并未意识到的编程范式,关注代码的结构组织,强调一个纯粹但在实际中有些理想化的不可变世界,涉及数学、方程和副作用等概念,甚至还有有趣的“柯里化”。接下来,我们将探讨函数式编程与以往编程方式的不同之处。
以下是一个简单的 Python 示例,展示了函数式编程中函数作为一等公民的特性:
# 定义一个简单的函数
def add(a, b):
return a + b
# 将函数作为参数传递给另一个函数
def apply_operation(func, x, y):
return func(x, y)
result = apply_operation(add, 3, 5)
print(result) # 输出: 8
编程范式就像一棵特殊的树,它展示了编程语言如何像口语语言一样分支成不同的家族。其中,最大的两个分支分别是命令式范式和声明式范式。
# 命令式风格:计算列表中所有偶数的和
numbers = [1, 2, 3, 4, 5, 6]
even_sum = 0
for num in numbers:
if num % 2 == 0:
even_sum += num
print(even_sum) # 输出: 12
# 声明式风格:计算列表中所有偶数的和
numbers = [1, 2, 3, 4, 5, 6]
even_sum = sum(filter(lambda x: x % 2 == 0, numbers))
print(even_sum) # 输出: 12
函数式范式位于声明式分支的大约中间位置,它概括了函数式编程与面向对象、过程式等常见范式相比所独有的概念和风格。
函数式范式的核心是函数,并且这些函数需要以较为不受限制的方式使用。这意味着我们可以将函数作为参数传递给其他函数,从其他函数中返回函数,还能保存对函数的引用以供后续使用。
# 定义一个函数,返回另一个函数
def create_multiplier(factor):
def multiplier(x):
return x * factor
return multiplier
# 创建一个乘以 3 的函数
triple = create_multiplier(3)
# 使用该函数
result = triple(5)
print(result) # 输出: 15
# 定义一个函数,返回一个闭包
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
# 创建闭包
closure = outer_function(10)
# 使用闭包
result = closure(5)
print(result) # 输出: 15
我们可以创建高阶函数,即与其他函数协作以执行特定操作的函数,如filter()
、sort()
、map()
等。这些高阶函数有助于创建可复用和独立的模块,使我们能够以更声明式的方式编写代码。
# 定义一个列表
numbers = [1, 2, 3, 4, 5]
# 使用 map 函数将列表中的每个元素平方
squared_numbers = list(map(lambda x: x ** 2, numbers))
print(squared_numbers) # 输出: [1, 4, 9, 16, 25]
# 使用 filter 函数过滤出列表中的偶数
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # 输出: [2, 4]
函数式编程追求不可变性,旨在避免副作用。副作用发生在函数外部的不可预测状态影响函数,或者函数对其外部作用域进行修改时。消除潜在的副作用可以使函数变得纯粹,即对于相同的输入数据,函数总是能保证产生相同的输出,且不会影响其他任何内容。这通常通过消除变量的可变性来实现。
# 纯函数示例
def add(a, b):
return a + b
# 非纯函数示例(有副作用)
counter = 0
def increment():
global counter
counter += 1
return counter
# 调用纯函数
result1 = add(2, 3)
print(result1) # 输出: 5
# 调用非纯函数
result2 = increment()
print(result2) # 输出: 1
# 定义一个普通的加法函数
def add(a, b):
return a + b
# 实现柯里化
def curry_add(a):
def inner(b):
return add(a, b)
return inner
# 使用柯里化函数
add_five = curry_add(5)
result = add_five(3)
print(result) # 输出: 8
def create_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
def get_count():
return count
return {
'increment': increment,
'get_count': get_count
}
# 创建一个计数器对象
counter = create_counter()
# 增加计数器的值
counter['increment']()
# 获取计数器的值
print(counter['get_count']()) # 输出: 1
前面介绍的只是函数式范式中常用的技术,而纯函数式范式代表着一个全新的世界,其中一切都是声明式、确定性的,并且理想情况下几乎永远不变。虽然表面上看这可能不太实用,但它源于数学领域,在数学中有很大的意义。在纯函数式范式中,主要处理类型和表达式,并遵循以下规则:
代码通常是被评估而不是被执行,这为我们带来了一些有趣的优化能力,如惰性评估和自动并行化。
# 定义一个生成器函数
def generate_numbers():
num = 0
while True:
yield num
num += 1
# 创建生成器对象
numbers = generate_numbers()
# 只获取前 5 个数字
for _ in range(5):
print(next(numbers))
不可变性在所有地方都被强制执行,这意味着当我们需要对数据进行更改时,是通过基于现有常量计算出新的常量来实现的。
# 定义一个不可变的元组
original_tuple = (1, 2, 3)
# 创建一个新的元组,基于原元组进行修改
new_tuple = original_tuple + (4,)
print(original_tuple) # 输出: (1, 2, 3)
print(new_tuple) # 输出: (1, 2, 3, 4)
为了保持函数的纯粹性,任何副作用的想法都被视为不可接受的,这就引入了单子的概念。单子是一种设计模式,用于处理函数式编程中的副作用。
class Maybe:
def __init__(self, value):
self.value = value
@staticmethod
def unit(value):
return Maybe(value)
def bind(self, func):
if self.value is None:
return Maybe(None)
return func(self.value)
# 定义一个函数,可能返回 None
def divide_by_two(x):
if x % 2 == 0:
return Maybe(x // 2)
return Maybe(None)
# 使用 Maybe 单子
result = Maybe(4).bind(divide_by_two)
print(result.value) # 输出: 2
纯函数式编程的世界很美好,但对于大多数程序员来说可能过于理想化。因此,我们通常会从纯函数式分支中选取一些实用的特性,并尽可能地加以利用。
无论你是函数式程序员、面向对象程序员,还是喜欢过程式代码的简洁性,都应保持开放的心态,勇于学习新知识。即使最终不使用函数式编程,学习新事物也永远不会是浪费时间。最后,感谢视频赞助商 RunMe,它为 VS Code 提供了一个完全免费且开源的扩展,可将基本的 Markdown 文件转换为完全交互式的笔记本,方便开发者测试代码片段、记录和分享工作流程。大家可以访问 RUNME.dev 了解更多信息,并加入他们的 Discord 社区参与讨论。