《类型和程序设计语言》

 

目录

《类型和程序设计语言》

百科

λ演算

归约策略

并行与并发

图灵机

停机问题

非确定图灵机

新浪博客:Lambda Calculus —— 叙述通俗朴素

我的最爱Lambda演算——开篇

Alpha转换

Beta规约

Good Math/Bad Math,原英文博客来一发:

再来看 知乎专栏《你好,类型》:

Gradual Typing

你好,类型(十):Parametric polymorphism

你好,类型(九):Let polymorphism

你好,类型(八):Subtype

你好,类型(七):Recursive type

你好,类型(六):Simply typed lambda calculus

你好,类型(五):Predicate logic

Hilbert-style和Gentzen-style演绎系统

你好,类型(四):Propositional logic

你好,类型(三):Combinatory logic

你好,类型(二):Lambda calculus

你好,类型(一):开篇

类型理论之拙见

形式证明与逻辑推理

数理逻辑定义汇总

模型观


《类型和程序设计语言》

 

 

百科

λ演算

λ演算,λ(Lambda(大写Λ,小写λ)读音:lan b(m) da(兰亩达)['læ;mdə])演算是一套用于研究函数定义、函数应用和递归的形式系统。它由 Alonzo Church 和 Stephen Cole Kleene 在 20 世纪三十年代引入,Church 运用 lambda 演算在 1936 年给出 判定性问题 (Entscheidungsproblem) 的一个否定的答案。这种演算可以用来清晰地定义什么是一个可计算函数关于两个 lambda 演算表达式是否等价的命题无法通过一个通用的算法来解决,这是不可判定性能够证明的头一个问题,甚至还在停机问题之先。

 

λ 演算可以被称为最小的通用程序设计语言。它包括一条变换规则 (变量替换) 和一条函数定义方式,λ演算之通用在于,任何一个可计算函数都能用这种形式来表达和求值。因而,它是等价于图灵机的。尽管如此,λ演算强调的是变换规则的运用,而非实现它们的具体机器。可以认为这是一种更接近软件而非硬件的方式。它一个数理逻辑形式系统,使用变量代入和置换来研究基于函数定义和应用的计算。希腊字母λ被用来在λ演算模型中表示将一个变量绑定在一个函数中。

λ演算可以是有类型的也可以是无类型的,仅仅当输入的的数据类型对于有类型的λ演算函数来说是可以接受的时,有类型的λ演算函数才能被使用。λ演算模型在数学,物理学,语言学和计算机科学等不同领域有着广泛的应用。它在编程语言的理论发展上起到了很重要的作用,并对函数式编程起到了很大的影响,甚至可以说函数式编程就是对λ演算模型的一种实现。同时,它也是范畴论的当前研究对象之一。

λ演算模型最初的形式系统在1935年被 Stephen Kleene 和 J. B. Rosser提出的Kleene–Rosser悖论证明为是前后矛盾的,接着,在1936年,Church单独出版了λ演算模型中的和纯计算有关的部分,也就是如今被称为的无类型λ演算。在1940年,他提出了一个弱化计算,但是逻辑自洽的形式系统,如今被称之为简单类型λ演算。

在20世纪60年代之前,λ演算和编程语言之间的关系被厘清之前,λ演算模型一直都仅仅是一个理论上的形式系统,多亏了Montague和其他的几位语言学家在自然语言的语义方面的研究,λ演算开始在语言学和计算机科学的研究中占有一席之地。

 

归约策略

在编程语言的理论研究中,求值策略(Evaluation strategy)是一组用来确定程序设计语言中的表达式求值的规则。求值策略主要规定了在什么时候和用什么样的顺序给函数的实际参数求值,何时把参数代换入函数内,和用怎样的形式来进行代换。通常,人们使用λ演算模型中的归约策略来建模求值策略。

无论一个表达式是否为标准状态,将这个这个表达式化为标准型所需要的工作量很大程度上依赖于归约策略的使用。而归约策略的不同又和函数式编程中的及早求值还有惰性求值之间的不同有关。

1.完全β-归约 (Full β-reduction)

任何参数在任何时候都可以被归约,其实就是没有任何的归约策略,天知道会发生什么。

2.应用次序 (Applicative order)

最右边,最内部的表达式总是首先被归约,直观上可以知道,这意味着函数的参数总是在函数调用之前就被归约了。应用次序总是企图用标准形式去调用函数,即便在很多时候这是不可能的。 大多数的程序设计语言(包括Lisp,ML和命令式语言C和Java等)都被描述为严格类型语言,意思是使用了不正确形式参数的函数是形式不正确的。它们在实际上就是使用了应用次序和传值调用归约,但通常被成为及早求值策略。

3.正常次序 (Normal order) 最左边,最外部的表达式总是首先被归约,这也就意味着无论什么时候,参数都是再被归约之前就被替换进了抽象的函数体里面了

4.传名调用 (Call by name) 和正常次序一样,但是不会在抽象的函数体中再进行归约,比如说,λx.(λx.x)x在这个策略中是正常形式, 虽然它包含了可归约的表达式(λx.x)x

5.传值调用 只有最外部的表达式被归约:一个表达式仅仅当它的右边已经被规约为一个值了才会被归约

6.传需求调用 “传需求调用”和传名调用类似,如果函数的实参被求值了,这个值就会被存储起来已备未来使用。它产生的结果和传名调用一样;但是如果函数的这个实参被调用了多次,那么传需求调用可以提高程序运行效率。它在现实语境中也被叫做惰性求值

并行与并发

函数式编程在一开始就是面向并发处理的,这也得益于lambda的性质,lambda演算的Church-Rosser性质意味着归约(β归约)可以以任何顺序进行,甚至是并行来进行。这意味着各种不同的非确定性归约策略都是相近的。然而,lambda演算并不提供任何直接的并行结构。一个人可以添加像Futures结构体这样的并发结构体到lambda演算中去。相关的进程代数已经为了进程通信和并发而被研究了出来。

在λ-演算的基础上,发展起来的π-演算、χ-演算,成为近年来的并发程序的理论工具之一,许多经典的并发程序模型就是以π-演算为框架的。

 

图灵机

图灵机 (Turing machine, TM) 是由图灵在1936年提出的,它是一种精确的通用计算机模型,能模拟实际计算机的所有计算行为。 [1] 

所谓的图灵机就是指一个抽象的机器,它有一条无限长的纸带,纸带分成了一个一个的小方格,每个方格有不同的颜色。有一个机器头在纸带上移来移去。机器头有一组内部状态,还有一些固定的程序。在每个时刻,机器头都要从当前纸带上读入一个方格信息,然后结合自己的内部状态查找程序表,根据程序输出信息到纸带方格上,并转换自己的内部状态,然后进行移动。

 

停机问题

停机问题(英语:halting problem)是逻辑数学可计算性理论的一个问题。通俗地说,停机问题就是判断任意一个程序是否能在有限的时间之内结束运行的问题。该问题等价于如下的判定问题:是否存在一个程序P,对于任意输入的程序w,能够判断w会在有限时间内结束或者死循环。

通俗的说,停机问题就是判断任意一个程序是否会在有限的时间之内结束运行的问题。如果这个问题可以在有限的时间之内解决,则有一个程序判断其本身是否会停机并做出相反的行为,这时候显然不管停机问题的结果是什么都不会符合要求。所以这是一个不可解的问题。

停机问题本质是一高阶逻辑的不自恰性和不完备性。类似的命题有理发师悖论全能悖论等。

 

非确定图灵机

定理:对于任意一个非确定型图灵机M,存在一个确定型图灵机M',使得它们的语言相等,即   。

 

定理2:如果语言L被非确定型图灵机 M在多项式时间内接受,则一定存在多项式P使得语言L被时间复杂度为  的确定型图灵机程序所接受。

定理2说明了为什么在证明P=NP之前,所有的NPC问题都只有指数时间复杂度算法。 [1] 

 


新浪博客:Lambda Calculus —— 叙述通俗朴素

http://blog.sina.com.cn/s/blog_68ffc7a4010169rl.html

在互联网行业中,每个人都希望自己的系统是能够水平扩展的,系统的计算能力也是如此。随着实践的深入,大家发现函数式编程能够天然得实现计算的并行化,实际上Map-reduce这样的并行框架本质上都是函数式编程思想下的产物。于是近些年来,Scala,Erlang这样的函数式编程语言越来越受到追捧,搞得程序员不会上个一两门函数式语言都不好意思出门打招呼。博主也不能免俗,准备也花点时间研究一下函数式编程,就从scala入手吧。在触及到具体的语言之前,我觉得还是很有必要先做一些理论储备,每一门计算机语言其实背后都蕴含着某一种思想的光芒。如果能够对这些基本的理论有一点体会或者理解,那么对于程序员提高自己的境界还是很有帮助的,至少在看到网上的大牛们讨论的时候不会一头雾水,经验上可能跟不上这些大牛,但思想一定要跟得上。函数式编程背后的理论基础就是Lambda calculus。作者花了点时间翻阅了一下维基百科http://en.wikipedia.org/wiki/Lambda_calculus,觉得有点体会,这里做个翻译点评。实际上维基百科中文也对该词条进行了翻译,但感觉中文只是英文词条的一个直译,很多东西含混不清,博主在这里对其进行一个评注,便于像我这样数学功底比较差的同仁们理解。

首先解释一下什么是Lambda caculus,Lambda 演算实际上是一套形式化系统,它的主要目的就是通过变量绑定以及代入的方法来表达计算,也就是说它能够从本质上来分析计算。正是因为如此,它在计算机理论界被广泛采用,成为设计函数式编程语言的理论工具。了解一点lamda演算,有助于你从本质上理解函数编程语言考虑问题和看待世界的方式。如果你能够理解到这个层次,也许你也能够在函数式编程思想的启迪下提出actor model这样的并行模型,成为万人膜拜的一代大牛。(憧憬一下....)

Lamda演算的概念十分重要,实际上Lambda 演算系统也不只一个,有uptyped Lambda calculus和typed Lambda calculus的区别,这两个演算系统实际上是不同的变种,至于到底有什么区别,我也不是很清楚,大概的意思是untyped Lambda calculus中的函数没有什么限制,而typed lamada calculus 中的函数所能接受的数据类型是有限制的。具体的区别,得靠理论界的专业人士来普及了,我也就不班门弄斧了。

Lambda演算的开山鼻祖是Alonzo Church,上个世纪30年代的大师。让我们记住他的名字吧。(好吧,只是记住他的名字,其实我也不知道他到底有多牛)

动机

首先来谈谈动机,Lambda演算到底想干什么。关键词就是函数function。我们知道在计算机语言中有一个很基本的概念就是递归函数,这个递归函数很烦人。记得在本科时学习C语言时,如果掌握了递归函数的写法,那C语言就算是基本入门了。而Lambda演算则为各式各样复杂的计算(包括递归计算)提供了一个统一的,简单的语义,从而使得我们能够正式得分析计算的各种特性。否则,计算只是一个口头上笼统的概念,有了Lambda演算,我们就有一个形式化系统来完整的分析到底什么是计算,它到底干了些什么,有什么特性。

首先让我们来看一个函数,identity function:

Id(x) = x

这个函数很简单,输入一个x,马上就返回这个输入值x。接下来再看另外一个函数,平方和函数:

Sqsum(x,y) = x*x + y*y

这个函数输入x,y,返回值是x和y的平方和x*x + y*y。

这是两个很简单的函数,通过观察这两个函数,我们可以观察到一些有用的东西,而这些观察正是Lambda演算主要思想的灵感来源。

首先第一个观察是,函数是不需要一个显式的名字的,比如函数

Sqsum(x,y) = x*x + y*y

其实可以写成一个匿名函数

(x,y) -> x*x +y*y

用专业一点的说法,这个函数可以表述成:

X,y对被映射成x*x + y*y。同样,类似的

Id(x) = x

也可以被写成一个匿名函数 x->x:输入被简单得映射成了自身。

第二个观察就是函数的参数(自变量)的名字是无关紧要的,这也就说

x->x

y->y

实际上表达的是同样的函数:identity function

类似的

(x,y) ->x*x + y*y 和

(u,v)->u*u + v*v

实际上表达的也是同一个函数。

最后一个观察就是,任何有两个自变量的函数,比如前面提到的sqsum函数,可以被重新写成另外一种形式的函数,这种新形式的函数只有一个输入变量,但输出返回的却是另外一个函数,而这个返回的函数同样也具有一个输入变量,输出返回另外一个函数,依次类推,直至终结。这有点和递归类似。举个例子吧,

(x,y) -> x*x + y*y

可以被写成

x->(y->x*x+y*y)

这种变化被称之为柯里化。有了柯里化,一个普通的具有多个输入自变量的函数都可以被转化成一系列的,只具有一个输入自变量的函数,这一系列的函数推导也被称之为partial application(片面应用)。

以上面的sqsum的例子而言,如果我们的输出参数是(5,2)我们可以进行如下推导:

((x,y) ->x*x + y*y)(5,2) = 5*5 + 2*2 = 29

如果使用了柯里化,则有

((x->(y->x*x+y*y))(5))(2)

=(y->5*5 + y*y)(2)

=5*5 + 2*2 = 29

我们看到柯里化和之前的函数都得到了一致的结果。需要注意的是,在函数链的第一个函数完成参数代入(x)之后,x*x就变成了一个常量。

Lambda演算

Lamda演算的描述是通过一种专门的Lambda词汇,这套词汇实际上定义了一种语法,以及基于该语法的一系列变换规则,这套规则可以操作Lambda词汇。这种变换规则可以被看作一种等价理论。正如上面所描述的,Lambda演算中的所有函数都是匿名函数,这些函数只有一个输入自变量,因为柯里化可以把多输入自变量的函数变换成只含有一个输入自变量的函数。

Lambda词汇

Lambda演算的语法定义了哪些表达式是有效的Lambda演算声明,同时也定义了哪些表达式不是有效的Lambda演算声明。就像C语言定义了哪些字符串是有效的C语言程序语法,哪些不是。一个有效的Lamda变化表达式被称之为一个Lambda词汇(Lambdaterm)。

下面的三条规则给出了一套归纳定义(递归定义),所有语法有效的Lambda词汇从根本上都源于这套归纳定义:

l 一个变量x,本身就是一个有效的Lambda词汇

l 如果t是一个Lambda词汇,x是一个变量,那么wps_clip_image-12780也是一个有效的Lambda词汇(也称之为Lambda抽象)

l 如果t和s都是Lambda词汇,那么ts也是一个有效的Lambda词汇(称之为application,中文叫应用)

任何其它的表达式都不是一个有效的Lambda词汇。如果一条表达式能够通过上述三条原则进行分解,那么这个表达式也可以称之为有效的Lambda词汇。

一个Lamda抽象wps_clip_image-32529实际上是一个匿名函数的定义,该函数接受一个输入自变量x,然后将x代入到表达式t中。举个例子,wps_clip_image-12783就是一个函数wps_clip_image-10509的Lamda抽象,表达式wps_clip_image-10204实际上就是t。Lamda抽象只不过建立了一个函数,但并没有调用这个函数,相当于函数声明。

而一个application ts表达的就是一种调用动作,就是把s作为输入代入到函数t中,从而产生t(s),相当于函数调用。

在Lambda演算中没有变量声明的概念。比如一个函数声明wps_clip_image-9956,Lambda演算就把y当作一个还没有定义的变量。Lambda抽象wps_clip_image-32672在语法上是有效的,它代表一个函数,这个函数的行为就是把输入变量x和一个暂时还未知的变量y相加。

Lambda词汇也利用括号来区分词条。比如wps_clip_image-23395wps_clip_image-8166表示的就是不同的词条。

函数

在Lambda演算中,函数是所谓的一等公民,也就说它和值本质上是一样的,函数可以作为输入,函数也可以作为其它函数的返回值出现。

还是举例子吧,wps_clip_image-28138代表着identical 函数 wps_clip_image-2152。而wps_clip_image-26042表示的是把identical函数应用到y上。另外一个例子,wps_clip_image-22542代表的是constant函数wps_clip_image-28706(函数永远返回y,不管输入x是什么)。

在Lambda演算中,函数的application是左优先的,也就说wps_clip_image-28904实际上意味着wps_clip_image-4357.

在Lambda演算中,有所谓的“等价”和“推导”的概念,这两个概念可以把某个Lambda词汇通过“推导”变成“等价”的Lambda词汇。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值