今天在看Build Your Own Lisp这本书的时候,遇到了部分计算和lambda演算的知识点,于是产生本文。
在计算机科学和数学中,我们可以从不同角度理解“函数”(Function)的概念:
在 数学模型 中,函数是一个 完全的映射关系,它接受输入,并确定性地产生输出,例如:
f ( x ) = x 2 f(x) = x^2 f(x)=x2
这里 f(2)
计算后一定是 4
,它是一个静态的关系。
在 计算模型 中,函数是一个动态的计算过程,它需要执行某些操作才能得出结果。例如:
def f(x):
return x * x
print(f(2)) # 输出 4
这个 Python 函数 f(x)
只有在 x
传入后才会执行。
除此之外,还有第三种方法来理解函数——Partial Computations(部分计算),它结合了数学模型的输入依赖性和计算模型的动态性,使得函数成为一个未完成的计算过程。本文将深入探讨这个概念,并介绍与其紧密相关的Lambda Calculus(λ演算)。
Partial Computations(部分计算) 是指函数在没有所有输入参数时,它的计算尚未完成,只有当缺失的输入补充完整,计算才能继续。
考虑以下 Python 代码:
def add(a, b):
return a + b
add(2, 3)
,它会立刻返回 5
。add(2, ?)
,这个计算就处于未完成状态,因为还缺少 b
的值。这就是部分计算的核心思想:函数的计算可以被“延迟”,直到所有参数都可用。
想象一个工厂生产线:
在 Partial Computations 中,Unbound Variables(未绑定变量) 是指 函数计算所需,但尚未提供的输入变量。
考虑以下 Python 代码:
def multiply(x, y):
return x * y
如果我们只提供 x = 3
,但 y
还不确定,那么 multiply(3, ?)
处于未完成状态,因为 y
是一个未绑定变量(Unbound Variable)。
想象你正在拼一副拼图,但有些拼图块还缺失:
在某些编程语言(如 Haskell)中,计算不会立即执行,而是等到所有参数可用时才执行。这让程序可以更高效地推迟计算,直到真正需要结果时才进行计算:
add :: Int -> Int -> Int
add x y = x + y
在 Haskell 中,我们可以只提供部分参数:
let f = add 3
这里 f
仍然是一个未完成的计算,只有当 f 4
这样调用时,才会真正计算 3 + 4
。
在 函数式编程 中,柯里化(Currying) 是 Partial Computations
的一种应用。柯里化允许我们将一个多参数函数拆分成多个单参数函数:
def add(x):
return lambda y: x + y
调用 add(3)
不会立即计算,而是返回一个新的函数:
add_three = add(3) # add_three 现在是一个等待 y 的函数
print(add_three(4)) # 输出 7
这样,我们可以一步步提供参数,直到所有参数齐全,计算才真正执行。
Lambda Calculus(λ演算) 是研究函数和计算的数学基础,它提供了一种用数学符号描述 Partial Computations 的方式。
在 λ演算中,我们可以这样定义一个加法函数:
λx.λy. (x + y)
这里:
λx.λy.
代表一个两层嵌套的匿名函数。(x + y)
是计算内容。λx. (λy. (x + y)) 3
,计算仍未完成。4
,即 (λy. (3 + y)) 4
,才会计算 3 + 4
。这种写法本质上和柯里化(Currying)相同,即:
add = \x -> \y -> x + y
在 λ演算中:
x
和 y
最初是未绑定变量(Unbound Variables)。x = 3
时,y
仍然是未绑定的。y = 4
时,所有变量都绑定,计算完成。这与Partial Computations 的概念完全一致!
理解 Partial Computations 不仅对函数式编程有帮助,它还影响了:
Lazy Evaluation
以提高性能。Unbound Variables
进行逻辑推导。希望这篇文章让你对 Partial Computations 和 Lambda Calculus 有更深入的理解!
In both computer science and mathematics, functions can be understood in different ways:
In the mathematical model, a function is a complete mapping that takes an input and produces a deterministic output.
Example:
f ( x ) = x 2 f(x) = x^2 f(x)=x2
Here, f(2)
is always 4
, making it a static relationship.
In the computational model, a function represents a dynamic process that executes when called.
Example in Python:
def f(x):
return x * x
print(f(2)) # Output: 4
The function f(x)
is only evaluated when executed, emphasizing its computational nature.
However, there’s a third way to think about functions—Partial Computations—which combines the input dependency of the mathematical model with the execution dynamics of the computational model. In this article, we’ll explore Partial Computations, Unbound Variables, and their connection to Lambda Calculus (λ-calculus).
Partial Computations refer to a state where a function has received some but not all of its required inputs, meaning its execution is still incomplete.
Consider this Python function:
def add(a, b):
return a + b
add(2, 3)
, it immediately evaluates to 5
.add(2, ?)
, the function remains partially computed because b
is still missing.This is the essence of Partial Computations—a function can remain in an incomplete state until all required inputs are provided.
Imagine assembling a car:
In Partial Computations, an Unbound Variable is an input required for computation but not yet provided.
Consider:
def multiply(x, y):
return x * y
x = 3
, but leave y
unspecified, the function remains incomplete.y
is an Unbound Variable, meaning the function cannot yet produce a final result.Imagine solving a jigsaw puzzle:
In some programming languages (like Haskell), computations do not execute immediately but are deferred until needed. This improves efficiency by ensuring calculations only happen when required.
Example in Haskell:
add :: Int -> Int -> Int
add x y = x + y
This function can be partially applied:
let f = add 3 -- `f` is a function waiting for another argument
Now, f
is still an incomplete computation. When we call f 4
, it finally computes 3 + 4
.
Currying is a technique that allows Partial Computations by transforming multi-argument functions into a series of single-argument functions.
Example in Python:
def add(x):
return lambda y: x + y
Calling add(3)
does not execute the computation immediately. Instead, it returns a new function:
add_three = add(3) # `add_three` is a function waiting for `y`
print(add_three(4)) # Output: 7
This demonstrates Partial Computation in action—we can progressively supply arguments until the computation is complete.
Lambda Calculus is a mathematical foundation for studying functions and computation. It provides a formal way to express Partial Computations.
In λ-calculus, a function is written as:
λx.λy. (x + y)
Here:
λx.λy.
represents a nested anonymous function.(x + y)
is the function body.x = 3
, the function remains incomplete.y = 4
, the computation finally executes.This is mathematically equivalent to currying:
add = \x -> \y -> x + y
The variable x
starts as an Unbound Variable, and only when x
and y
are both bound does the function complete.
λx. (λy. (x + y))
, initially, x
and y
are Unbound Variables.x = 3
makes y
the only remaining Unbound Variable.y = 4
, both variables are bound, and the computation executes fully.This shows that Partial Computations and Unbound Variables naturally arise in functional programming and mathematical logic.
Understanding Partial Computations is beneficial for:
By grasping these concepts, we bridge the gap between mathematics, logic, and computation, leading to more powerful programming paradigms!
2025年2月5日于山东日照。在GPT4o大模型辅助下完成。