(阅读笔记,低质,请直接翻看引用)
强烈推荐 https://www.youtube.com/watch?v=FITJMJjASUs
------更新---------
最近在看TAPL,遇到了lambda-calculus,这算是我第一次系统性的学习这个概念。然后就像了解到信息论和图灵机一样,跪倒在地。按照自己不记录一下就没有学透彻的惯例,记录一下学习心得。
- Functions are the only data type
- λ \lambda λ binding is the only way to associate values to variables
- Calculation happens via Beta (or Alpha) reduction
lambda 相关约定
t : : = t e r m s x v a r i a b l e λ x . t a b s t r a c t i o n t t a p p l i c a t i o n t ::= \qquad \qquad \qquad \qquad terms\\ \qquad x \qquad \qquad \qquad\qquad variable \\ \qquad \lambda x.t \qquad \qquad \qquad abstraction \\ \qquad t \ t \qquad \qquad \qquad application t::=termsxvariableλx.tabstractiont tapplication
- application associates to the left,例如 s t u s\ t\ u s t u,左结合就是 ( s t ) u (s\ t)\ u (s t) u。
- the bodies of abstractions are taken to extend as far to the right as possible,例如 λ x . λ y . x y x \lambda x. \ \lambda y.\ x\ y\ x λx. λy. x y x,首先 λ x . ( λ y . x y x ) \lambda x. \ (\lambda y.\ x\ y\ x) λx. (λy. x y x),然后 λ x . ( λ y . ( x y x ) ) \lambda x. \ (\lambda y.\ (x\ y\ x)) λx. (λy. (x y x)),下面就是最里面的左结合 λ x . ( λ y . ( ( x y ) x ) ) \lambda x. \ (\lambda y.\ ((x\ y)\ x)) λx. (λy. ((x y) x))。
- λ y . x y \lambda y. \ x \ y λy. x y中的 x x x是free的
- ( λ x . x ) x (\lambda x.x)\ x (λx.x) x中最后面的x是free的
- 如果在一个term中没有free variable,那么term就是closed的,被称作combinators。最简单的combinator是 i d = λ x . x id = \lambda x.\ x id=λx. x
柯里化
In the lambda-calculus everything is a function: the arguments accepted by functions are themselves functions and the result returned by a function is another function.
每个abstraction只能限定一个参数,例如 λ x \lambda x λx只有一个参数 x x x,那么 λ \lambda λ只能处理一个参数吗?
In mathematics and computer science, currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of function, each with a single argument.
我比较熟悉SML,SML的柯里化如下代码:
fun add x y = x + y;
val add3 = add 3;
val res = add3 4;
关于Alpha转换和Beta规约的内容,我的最爱Lambda演算——开篇讲的比较浅显易懂。
Call by Value
Call by value means: reduce the argument to normal form and then bind the parameter to it
对于
(
λ
x
.
x
)
(
(
λ
x
.
x
)
(
λ
z
.
(
λ
x
.
x
)
z
)
)
(\lambda x.\ x)\ ((\lambda x.\ x)\ (\lambda z.\ (\lambda x.\ x)\ z))
(λx. x) ((λx. x) (λz. (λx. x) z))这个reducible expression,我们可以按照下面的方式来进行reduction,也就是转化成
a
p
p
l
i
c
a
t
i
o
n
application
application。这种方式就是call by value,allowing no reductions inside abstractions。
call by value被应用在了很多种编程语言中,例如我熟知的C++和Golang,本质上就是在函数调用之前先把参数的值求出来,然后再调用。
Call by Name
Call by name means: replace each occurrence of the parameter in the body of the function by the unevaluated argument
对于call-by-name来说,就是先replace,然后再evaluate evaluate,是一种lazy的方式,一些函数式语言中有这样的方式。
Haskell uses an optimized version known as call-by-need that, instead of re-evaluating an argument each time it is used, avoiding the need for sub-sequent re-evaluation.
Church booleans
lambda演算是一种自洽的系统,其中布尔和自然数和我们常识中的可能不太一样。Church booleans定义如下:
t
r
u
e
=
λ
t
.
λ
f
.
t
true = \lambda t.\ \lambda f.\ t
true=λt. λf. t
f
a
l
s
e
=
λ
t
.
λ
f
.
f
false = \lambda t.\ \lambda f.\ f
false=λt. λf. f
所以true和false本身也是一个函数,true返回第一个参数的值,false返回第二个参数的值。为此我们可以定义另外一个application,
t e s t = λ l . λ m . λ n . l m n ; test \ =\ \lambda l.\ \lambda m.\ \lambda n.\ l\ m\ n; test = λl. λm. λn. l m n;
对于 t e s t b v w test\ b\ v\ w test b v w而言,如果 b 为 true,则返回v,如果b为false则返回第二个w。
例如对于
t
e
s
t
t
r
u
e
v
w
test\ true\ v\ w
test true v w,整个evaluation的过程如下:
这里有一个小trick,就是church boolean不只是表征的一个状态,它还会参与到后续的evaluate过程中。
类似我们还可以定义 a n d and and运算符, a n d and and接受两个实参,返回一个值,如果两个实参都为 t r u e true true则返回 t r u e true true,否则返回 f a l s e false false。
a n d = λ m . λ n . m n f a l s e and\ =\ \lambda m.\ \lambda n.\ m\ n\ false and = λm. λn. m n false
o r or or运算符如下,
o r = λ m . λ n . m t r u e n or\ =\ \lambda m.\ \lambda n.\ m\ true\ n or = λm. λn. m true n
n
o
t
not
not运算符如下,
n
o
t
=
λ
m
.
m
f
a
l
s
e
t
r
u
e
not\ =\ \lambda m.\ m\ false\ true
not = λm. m false true
Church Pairs
church pairs是比较精巧的,定义如下
p
a
i
r
=
λ
f
.
λ
s
.
λ
b
.
b
f
s
pair\ =\ \lambda f.\ \lambda s.\ \lambda b.\ b\ f\ s
pair = λf. λs. λb. b f s
f
s
t
=
λ
p
.
p
t
r
u
e
fst\ =\ \lambda p.\ p\ true
fst = λp. p true
s
e
c
o
n
d
=
λ
p
.
f
a
l
s
e
second\ =\ \lambda p.\ false
second = λp. false
例如 p a i r v w = λ b . b v w pair\ v\ w= \lambda b.\ b\ v\ w pair v w=λb. b v w,右侧可以看做左侧柯里化以后的结果。而 f s t fst fst或者 s n d snd snd可以将剩下的参数填充进去。
Church numerals
类似于
c
h
u
r
c
h
b
o
o
l
e
a
n
s
church\ booleans
church booleans,
c
h
u
r
c
h
n
u
m
e
r
a
l
s
church\ numerals
church numerals将一系列的自然数定义为了一组函数。
c
0
=
λ
s
.
λ
z
.
z
;
c_0\ = \lambda s.\ \lambda z.\ z;
c0 =λs. λz. z;
c
1
=
λ
s
.
λ
z
.
s
z
;
c_1\ = \lambda s.\ \lambda z.\ s\ z;
c1 =λs. λz. s z;
c
2
=
λ
s
.
λ
z
.
s
(
s
z
)
;
c_2\ = \lambda s.\ \lambda z.\ s\ (s\ z);
c2 =λs. λz. s (s z);
c
3
=
λ
s
.
λ
z
.
s
(
s
(
s
z
)
)
;
c_3\ = \lambda s.\ \lambda z.\ s\ (s\ (s\ z));
c3 =λs. λz. s (s (s z));
用 s s s的个数来表征具体的数值, z z z可以看做一个辅助性结尾的功能。我们可以看到,对任何数字,例如 c 0 c_0 c0传入 c 0 s z c_0\ s\ z c0 s z得到的还是 c 0 c_0 c0本身。
对应的
s
c
c
scc
scc可以定义成下面的形式,最前面的
s
s
s就相当于在
s
s
s的计数上加了一个1。
s
u
c
c
=
λ
n
.
λ
s
.
λ
z
.
s
(
n
s
z
)
succ\ = \lambda n.\ \lambda s.\ \lambda z.\ s\ (n\ s\ z)
succ =λn. λs. λz. s (n s z)
加法如下所示,可以看到
m
m
m后面的
s
s
s起着一个连接的作用。
p
l
u
s
=
λ
m
.
λ
n
.
λ
s
.
λ
z
.
m
s
(
n
s
z
)
plus\ =\lambda m.\ \lambda n.\ \lambda s.\ \lambda z.\ m\ s\ (n\ s\ z)
plus =λm. λn. λs. λz. m s (n s z)
关于church numerals的加减乘除,CSE 340 11-30-15 Lecture: "Lambda Calculus Pt. 3中有非常好的解释。
p o w e r power power如下所示:
p o w e r = λ m . λ n . m n power=\lambda m.\ \lambda n.\ m\ n power=λm. λn. m n
Recusion
这一部分是最复杂的,可以查看康托尔、哥德尔、图灵——永恒的金色对角线,刘未鹏的推导也是极其复杂,这里采用Clear, intuitive derivation of the fixed-point combinator (Y combinator)?和《Programming Languages and Lambda Calculi》的推导过程。
只想直观感受lambda calculus中的recusion是如何实现的,可以参考CSE 340 12-2-15 Lecture: "Lambda Calculus Pt. 4 and Midterm 2 Review"和Essentials: Functional Programming’s Y Combinator - Computerphile。
我们先思考一个问题,如何在lambda calculus中实现阶乘?对于有编程经验的人,第一反应应该是下面的代码:
f a c t = λ n . if iszero n then 1 else n ∗ f a c t ( pred n ) fact=\lambda n.\ \textbf{if}\ \textbf{iszero}\ n\ \\ \qquad \qquad \quad \textbf{then}\ 1 \\ \qquad \qquad \quad \textbf{else}\ n * fact(\textbf{pred}\ n) fact=λn. if iszero n then 1else n∗fact(pred n)
但是上面的代码在lambda calculus中是不可能实现的。因为本质上lambda calculus都是“匿名”的。也就是我们需要填满下面的something,
f a c t = λ n . if iszero n then 1 else n ∗ s o m e t h i n g ( pred n ) fact=\lambda n.\ \textbf{if}\ \textbf{iszero}\ n\ \\ \qquad \qquad \quad \textbf{then}\ 1 \\ \qquad \qquad \quad \textbf{else}\ n * something(\textbf{pred}\ n) fact=λn. if iszero n then 1else n∗something(pred n)
一种可能的方式就是把something提出来做成一个参数,然后在apply的时候,填充进去。如下所示的参数f,
f a c t = λ f . ( λ n . if iszero n then 1 else n ∗ f ( pred n ) ) fact=\lambda f.\ (\lambda n.\ \textbf{if}\ \textbf{iszero}\ n\ \textbf{then}\ 1\ \textbf{else}\ n * f(\textbf{pred}\ n)) fact=λf. (λn. if iszero n then 1 else n∗f(pred n))
能不能把上面的body复制一遍呢?如下所示:
f a c t = ( λ f . ( λ n . if iszero n then 1 else n ∗ f ( pred n ) ) ) ( λ f . ( λ n . if iszero n then 1 else n ∗ f ( pred n ) ) ) fact=(\lambda f.\ (\lambda n.\ \textbf{if}\ \textbf{iszero}\ n\ \textbf{then}\ 1\ \textbf{else}\ n * f(\textbf{pred}\ n)))\ (\lambda f.\ (\lambda n.\ \textbf{if}\ \textbf{iszero}\ n\ \textbf{then}\ 1\ \textbf{else}\ n * f(\textbf{pred}\ n))) fact=(λf. (λn. if iszero n then 1 else n∗f(pred n))) (λf. (λn. if iszero n then 1 else n∗f(pred n)))
那这样可不可行呢?其实不太可行,进行到第(5)步的时候,我们少了一个实参,也就是
f
f
f。
那么怎么才可以呢?上面的形式太复杂,我们用另外一种方式呈现,例如下面的递归形式,我们要在
f
f
f的body里调用
f
f
f。
f = . . . . f . . . f . . . f = \ ....\ f\ ...\ f\ ... f= .... f ... f ...
为了达到递归的目的,我们需要将f提出来,然后将其作为参数,也就是下面的形式。
f
=
(
λ
r
.
(
.
.
.
r
.
.
.
r
.
.
.
)
)
f
f = (\lambda r.(...r\ ...r\ ...))f
f=(λr.(...r ...r ...))f
f
=
(
λ
r
.
(
.
.
.
r
.
.
.
r
.
.
.
)
)
(
λ
r
.
(
.
.
.
r
.
.
.
r
.
.
.
)
)
f = (\lambda r.(...r\ ...r\ ...))(\lambda r.(...r\ ...r\ ...))
f=(λr.(...r ...r ...))(λr.(...r ...r ...))
现在我们有了什么呢?我们有了M,也就是一个被抽离了自我调用的带有body的形式。此时就回到了我们最开始的尝试,当然这种尝试并没有什么有意义的进步。
上面的其实不是递归的,因为里面已经没有
f
f
f了,怎样才能在
b
o
d
y
body
body里构造出来
f
f
f呢,可能的一种方式就是构造
M
M
MM
MM出来,如下所示,如果我们用
M
M
M替换其中的
x
x
x,然后
r
r
r替换成
x
x
xx
xx,那么里面就会出现
M
M
MM
MM,也就是
f
f
f。但是下面的形式不是递归的,因为需要实参
M
M
M的参与,才能构造出
f
f
f本身。
我们暂时放弃在
f
f
f上构造递归的可能,先把上面的形式泛化,注意其中的
M
M
M是真正的函数体。我们可不可以把函数体
M
M
M提出来,如下所示:
此时我们有了一个成为
Y
Y
Y的东西,
Y
M
Y\ M
Y M就会构造出
f
f
f。但是这个东西有什么卵用呢?这个东西有下面的性质,就是
Y
M
=
Y
(
Y
M
)
Y\ M = Y\ (Y\ M)
Y M=Y (Y M)。由于
f
=
Y
M
f=Y\ M
f=Y M,所以
Y
Y
Y有这样的性质,就是将它应用在我们想要递归的函数体
M
M
M上,那么就会源源不断的产生
M
M
M,也就是
Y
M
=
M
(
M
(
.
.
.
(
Y
M
)
)
)
Y\ M=M\ (M(...(Y\ M)))
Y M=M (M(...(Y M)))。至此我们大概已经意识到我们想要构造出来的
f
f
f其实就是
M
M
M。
综上lambda calculus上真正的递归是无法实现的,但是我们可以通过一个
Y
c
o
m
b
i
n
a
t
o
r
Y combinator
Ycombinator间接实现的递归,如下所示:
注意到,这里我们需要将
Y
c
o
m
b
i
n
a
t
o
r
Y combinator
Ycombinator作为参数传进去,这也解释了我们刚开始构造递归形式失败的原因,也就是
f
=
M
M
f=M\ M
f=M M失败的原因,因为它只能进行两次
M
M
M的递归调用,而
Y
Y
Y就是一个源源不断的
M
M
M生成器。其实
Y
Y
Y内部借助了下面
o
m
e
g
a
omega
omega的形式,
o
m
e
g
a
omega
omega的特点就是不断地重复自己,一个死循环。正由于
o
m
e
g
a
omega
omega是lambda calculus能实现递归的核心,所以TAPL将其放在了recursion这一节的开头。
前面看似几步就推出了
Y
c
o
m
b
i
n
a
t
o
r
Y\ combinator
Y combinator,但这都是后事之师,真正从一张白纸找到
Y
c
o
m
b
i
n
a
t
o
r
Y\ combinator
Y combinator还是极其困难的。
https://cgnail.github.io/academic/lambda-1/
https://cgnail.github.io/academic/lambda-2/
https://cgnail.github.io/academic/lambda-3/
https://cgnail.github.io/academic/lambda-4/
https://liujiacai.net/blog/2014/10/12/lambda-calculus-introduction/
https://blog.csdn.net/g9yuayon/article/details/748684
https://blog.csdn.net/pongba/article/details/1336028
https://github.com/txyyss/Lambda-Calculus/releases
https://www.youtube.com/watch?v=6goESiHNhIo
http://www-sop.inria.fr/members/Yves.Bertot/misc/types-eng.pdf
https://en.wikipedia.org/wiki/Lambda_calculus
http://fsl.cs.illinois.edu/images/2/26/CS522-Spring-2013-Lambda-and-Combinators.pdf
https://www.youtube.com/watch?v=FITJMJjASUs
http://www.cs.bham.ac.uk/~axj/pub/papers/lambda-calculus.pdf
http://palmstroem.blogspot.com/2012/05/lambda-calculus-for-absolute-dummies.html
https://www.youtube.com/watch?v=SphBW9ILVPU&feature=youtu.be&t=575
https://news.ycombinator.com/item?id=19835615
https://news.ycombinator.com/item?id=13258037
https://mvanier.livejournal.com/2897.html
Lambda calculus: Call by value / Call by name (lazy)