# 函数式编程

2023年8月11日9分钟阅读

- FormalSciences/ComputerScience
- ProgrammingLanguage/Python
- Python/Advanced

## 函数式编程

### 函数式编程是什么

函数式编程（Functional Programming, FP）是一种编程范式，**主张用数学上的函数方式构建结构和元素之间的关系，而不是改变状态和数据。**在函数式编程中，函数是第一公民，这意味着函数可以被传递、返回和操作，就像其他的数据类型一样。函数在这里是「纯」的，意味着相同的输入始终产生相同的输出，并且没有副作用。

    # 函数作为参数传递给另一个函数
    def apply(func, value):
        return func(value)

    result = apply(lambda x: x*2, 5)  # 输出10

### 函数式编程与其他编程范式的区别

函数式编程与命令式编程的区别在于，命令式编程关注如何完成任务，强调程序状态和改变状态的语句，而**函数式编程注重数据的映射和组合。**面向对象编程重视对象及其之间的交互，而函数式编程重视函数和数据处理。

### 函数式编程的优势和局限性

优势：

- 简洁性：函数式编程往往更简洁，可以用更少的代码做更多的事情。
- 可维护性：由于函数式编程的代码没有副作用，它通常更容易维护和调试。
- 可重用性：函数是高度模块化的，可以在多个地方重用。

局限性：

- 内存使用：由于函数式编程倾向于复制数据而不是改变它，它可能使用更多的内存。
- 难度：对于不熟悉该范式的开发者来说，函数式编程可能较难学习。

## Python 中的基础函数式工具

Python 提供了一些内置的函数式工具，如 `lambda`，`map()`，`filter()` 和 `reduce()`，它们可以帮助你以函数式的方式处理数据。

### `lambda`

`lambda` 允许我们定义简短的匿名函数。

    double = lambda x: x * 2
    print(double(5))  # 输出10

### `map()`

`map()` 函数将指定函数应用于序列的每一个元素。

    nums = [1, 2, 3, 4]
    squared = list(map(lambda x: x**2, nums))  # 输出[1, 4, 9, 16]

### `filter()`

`filter()` 函数根据指定函数的判断结果来过滤序列。

    nums = [1, 2, 3, 4, 5]
    evens = list(filter(lambda x: x % 2 == 0, nums))  # 输出[2, 4]

### `reduce()`

`reduce()` 函数对序列中的元素进行连续、累计地应用指定函数。

    from functools import reduce

    nums = [1, 2, 3, 4]
    product = reduce(lambda x, y: x*y, nums)  # 输出24

### `functools` 模块

`functools` 模块提供了一些用于函数式编程的实用工具，如偏函数等。

    from functools import partial

    def multiply(x, y):
        return x * y

    double = partial(multiply, 2)
    print(double(4))  # 输出8

## 高阶函数

### 高阶函是数什么

高阶函数接受一个或多个函数作为参数，或者返回一个函数。

    def greet(type_):
        if type_ == 'hello':
            return lambda name: "Hello, " + name
        else:
            return lambda name: "Hi, " + name

    greeting = greet('hello')
    print(greeting('Alice'))  # 输出'Hello, Alice'

### 在 Python 中创建和使用高阶函数

除了 Python 内置的如 map、filter 和 reduce 这样的高阶函数外，我们也可以创建自己的高阶函数。

    def apply(func, data):
        return [func(item) for item in data]

    nums = [1, 2, 3, 4]
    result = apply(lambda x: x*2, nums)  # 输出[2, 4, 6, 8]

## 纯函数和不变性

### 纯函数是什么

纯函数是函数式编程的核心概念之一。一个函数被认为是纯的，当它满足以下条件时：

- 给定相同的输入，总是返回相同的输出。
- 没有任何副作用（例如修改全局状态、修改传入的参数、进行 I/O 操作等）。

这些特性使纯函数变得可预测且容易测试。

    # 纯函数示例
    def add(x, y):
        return x + y

    # 不纯的函数示例，因为它改变了外部状态
    counter = 0
    def increment():
        global counter
        counter += 1
        return counter

### 数据的不变性

在函数式编程中，数据是不可变的。这意味着一旦一个数据结构被创建，就不能再改变它。而是每次需要修改数据时，都会返回一个新的数据副本。

这一特性增加了代码的可读性和可预测性，因为你不必担心数据在不知情的情况下被修改。

    # 使用列表作为示例
    lst = [1, 2, 3]

    # 错误的做法：修改原始列表
    lst.append(4)

    # 正确的做法：创建新的列表
    new_lst = lst + [4]

## 装饰器

### Python 中的装饰器

装饰器是 Python 中的一个强大工具，它允许开发者**在不修改原始函数代码的情况下，给函数增加新的功能。**它们通常用于日志、权限检查、统计或其他跨越多个函数或方法的通用任务。

    def my_decorator(func):
        def wrapper():
            print("Something is happening before the function is called.")
            func()
            print("Something is happening after the function is called.")
        return wrapper

    @my_decorator
    def say_hello():
        print("Hello!")

    say_hello()

### 如何利用装饰器优化代码

如上所示，装饰器是一个返回另一个函数的函数。要使用装饰器，只需在你想要装饰的函数上方加上 `@decorator_name`。

    def repeat(num):
        def decorator_repeat(func):
            def wrapper(*args, **kwargs):
                for _ in range(num):
                    result = func(*args, **kwargs)
                return result
            return wrapper
        return decorator_repeat

    @repeat(num=4)
    def greet(name):
        print(f"Hello, {name}")

    greet("Alice")  # 输出四次 "Hello, Alice"

## 闭包和自由变量

闭包是一种特殊的函数，它可以记住在其所在作用域中声明的自由变量的值，即使它们在函数外部是不可用的。在更简单的语言中，闭包允许函数携带与之相关的数据。

在 Python 中，当内部函数引用了外部函数中的变量，内部函数就被认为是闭包。

    def outer_function(x):
        def inner_function(y):
            return x + y
        return inner_function

    closure = outer_function(10)
    print(closure(5))  # 输出15

## 递归

递归是一种编程技巧，其中函数调用自身以解决较小的问题实例。递归通常与某种终止条件结合使用，以防止无限的自我调用。递归函数的经典例子是计算阶乘：

    # 此函数会不断调用自己，直到n为1。
    def factorial(n):
        if n == 1:
            return 1
        return n * factorial(n - 1)

    print(factorial(5))  # 输出120

### 递归、迭代对比

对比点 | 递归 | 迭代
--- | --- | ---
直观性 | 通常更直观和更容易实现 | 通常需要使用循环结构，可能不如递归直观
自然选择 | 对于某些问题，如树的遍历，递归是自然的选择 | 对于基本的数据结构，如数组和链表，迭代是自然选择
函数调用 | 可能会导致大量的函数调用，从而可能达到调用堆栈的限制 | 由于是循环结构，不会导致函数调用的堆栈溢出
效率 | 对于大量的递归，可能不如迭代高效 | 对于简单的循环，迭代可能更加高效
内存使用 | 每次调用自己都需要额外的内存来存储变量和信息，可能会导致调用堆栈溢出 | 通常更为内存高效，因为它不需要为每次循环存储额外的信息
实现方式 | 函数调用自己，直到满足某个条件 | 使用循环结构，如 `for` 和 `while`