λ演算入门
λ演算入门
写在前面
学这个主要是因为我们社团准备要规范一些写代码的习惯以及结构 确定了OOP以及函数化编程后在寻找适合函数化编程的伪代码规范的时候决定恶补一下底层知识
本文适合对于计算机科学与代数有一定了解的同学 最好是python 我文中就先用py的lambda expression来做的implementation
我自己做了个-calculus的演算器,可以来玩玩 网站 GitHub
结尾有Cheatsheet哈~
0x01 Notations
Assume a mathematical function . It can be denoted as a map:
Lambda calculus is all about the manipulation of functions, so the definition of functions is necessary. In lambda calculus, functions are called λ-abstractions:
Definition
λ-Abstractions represents anonymous singled-valued functions. Consider a map then a equivalent λ-abstraction could be defined as
Where is the bound variable and the body. In modern programming languages, is also known as the input parameter.
Thus our squaring function could be defined using a lambda abstraction:
Warning
Note that the abstraction is not equivalent of the following py3 function:
def f(x):
return x ** 2
is the body of the abstraction. In λ-calculus, there's no concept as to return values; an abstraction is only a mathematical object. Analogy:
- Functions in languages are like calculators (produce values immediately).
- λ-abstractions are like equations (preserve structure for formal manipulation).
Instead, python and many other languages have a built-in lambda syntax:
(lambda x: x ** 2) # Defines a lambda function that squares
(lambda x: x ** 2) (2) # Outputs 4
0x02 Reductions
λ-abstractions could be manipulated. Consider the previous python lambda function:
(lambda x: x ** 2)
It represents a λ-abstraction . One can notice that changing the bound variable of the abstraction does not change the essence of the map, ie whether the abstraction is called or it is always equivalent to our initial abstraction. Therefore we define α-conversion as follows:
Definition
α-conversions is a operation on a λ-abstraction to change the bound variable. For an abstraction with bound variable x and body M:
is an α-conversion.
An example would be to da an α-conversion on our previous python lambda expression.
>>> f1 = (lambda x: x ** 2) # Original Function
>>> f2 = (lambda y: y ** 2) # Alpha-conversion done
>>> assert f1 (2) == f2 (2) # Check for equivalence
True
Functions on their own aren't of much use. To utilize a function, function applications come in:
Definition
β-reduction is a function application where a specific value is being computed using a λ abstraction. For a given abstration with bound variable x and body M:
For example, consider the algebriac function and computing , the equivalent in lambda calculus would be
Let's try that out on our previous function.
>>> f = (lambda x: x ** 2)
>>> f (2) # Applies 2 to the parameter x, thus computing x ** 2 [2/x]
4
Note that lambda calculus is completely symbolic, therefore numerical operations are not allowed. The squaring example is included purely for demonstration.
Until here, one limitation of lambda abstractions is shown: they are single-valued functions. However, the higher-order property of lambda abstractions allows them to recieve functions as inputs. Consider the following implementation of binary addition:
It implements something called currying, named after the logician Haskell Curry. The brackets are included only for easier understanding; the lambda abstract itself could be written as follows:
Implementation
In python, a curried lambda function could also be implemented as follows:
(lambda x: lambda y: x + y) (1) (2) # Outputs 3
Although it seems like that a nested function requires strict ordering of parameters, it is not the case in a curried function.
Proof Box
Lemma. Any two abstractions of form and (y, x) are equivalent.
Proof. Perform α-conversions on f and g:
Therefore .
0x03 Encoding
The lambda calculus is pretty primitive as for now. However, according to the Church-Turing thesis, this functional programming language is Turing Complete, thus capable of anything a modern programming language is.
Let's see some implementations of features in modern languages.
Booleans and Conditionals
In lambda calculus, all computation is expressed through function abstraction and application. We can define Boolean values as selector functions:
If one extract any one of these lambda functions into a python named function, it looks like this:
def true(x, y):
x()
def false(x, y):
y()
It is already obvious that these are control-flow statements. They might seemed hard-coded, but try seeing it from a perspective when they are variables.
The conditional operator can be implemented as:
Reduction Examples:
Therefore a valid control-flow statement has been implemented. By this logic, even a switch statement with finite number of branches can be implemented. For example, a ternary switch-case control selector could be implemented.
First, the cases/branches need to be defined:
Then the conditional itself:
Then the selector can be called as
Here's an implementation in python;
>>> A = (lambda x: lambda y: lambda z: x)
>>> B = (lambda x: lambda y: lambda z: y)
>>> C = (lambda x: lambda y: lambda z: z)
>>> Selector = (lambda s: lambda x: lambda y: lambda z: s (x) (y) (z))
>>> Selector (A) ('a') ('b') ('c')
'a'
>>> Selector (B) ('a') ('b') ('c')
'b'
>>> Selector (C) ('a') ('b') ('c')
'c'
Boolean Operations
Once we have the boolean operations written, boolean operants could be easily implemented. For example, not could switch into two branches based on the value of the bound variable:
And could be implemented through a brute-force check of combination of bound vars:
Further more, the statement can be ommited completely:
Proof Box
Lemma. Any application of the form is equivalent to .
Proof. Perform a -reduction on the application:
Other binary operands can be implemented similarly:
Operand | |
---|---|
For more implementations, see cheatsheet
Church Numerals
Appendix: Cheatsheet
Boolean Constants
T (True)
- Lambda Abstraction:
- Python Implementation:
T = lambda a: lambda b: a
- Effect:
Selects first argument: - Example:
>>> T ("T") ("F") "T"
F (False)
- Lambda Abstraction:
- Python Implementation:
F = lambda a: lambda b: b
- Effect:
Selects second argument: - Example:
>>> F ("T") ("F") "F"
Unary Boolean Gates
NOT
- Lambda Abstraction:
- Python Implementation:
NOT = lambda s: s (F) (T)
- Effect:
- Example:
>>> NOT (T) ("T") ("F") "F"
Binary Boolean Operations
AND
- Lambda Abstraction:
- Python Implementation:
AND = lambda a: lambda b: a (b) (F)
- Effect:
- Example:
>>> AND (T) (F) ("T") ("F") "F"
OR
- Lambda Abstraction:
- Python Implementation:
OR = lambda a: lambda b: a (T) (b)
- Effect:
- Example:
>>> OR (F) (T) ("T") ("F") "T"
NAND
- Lambda Abstraction:
- Python Implementation:
NAND = lambda a: lambda b: a (NOT (b)) (T)
- Effect:
- Example:
>>> NAND (T) (T) ("T") ("F") "F"
NOR
- Lambda Abstraction:
- Python Implementation:
NOR = lambda a: lambda b: a (F) (NOT (b))
- Effect:
- Example:
>>> NOR (F) (F) ("T") ("F") "T"
XOR
- Lambda Abstraction:
- Python Implementation:
XOR = lambda a: lambda b: a (NOT (b)) (b)
- Effect:
- Example:
>>> XOR (T) (F) ("T") ("F") "T"
Key Conventions
- All operations are curried:
AND (T) (F)
notAND(T, F)
- Church booleans act as selectors:
- Primitive definitions:
T = lambda a: lambda b: a # Church TRUE F = lambda a: lambda b: b # Church FALSE
- 0
- 0
- 0
- 0
- 0
- 0