语言的作用
语言的作用是为了交流想法,描述概念,
当前使用了什么语言,取决于我们有什么样的需要。
为了理解词法作用域,闭包,和continuation,
前文中,我们借助了Racket。
现在,为了理解代数数据类型(algebraic data type),多态(polymorphism),参数化类型(parameterized type),类型类(type class),我们要学习Haskell了。
编程也是如此,它是关于思想的,
编程语言只是描述这种思想的工具罢了。
非严格语义(non-strict semantics)
在Haskell规范中,并没有要求使用惰性求值策略(evaluation strategy),
只是规定它是一种非严格的语言(non-strict language),
具体的求值策略取决于实现。
那么,什么才能叫做non-strict呢?
non-strict与lazy有什么关系呢?
还要从数学上函数的严格性(strictness)说起。
程序中的function与数学函数之间的关系,
是指称语义中的内容。(指称语义是形式语义的一种,它将每一段代码,与一个数学对象相对应,用来研究程序的含义。
在数学上,如果一个函数对定义域中的某些参数,没有定义值,
就称为该函数为部分函数(partial function)。
程序中的function,对应着数学上的部分函数。
此外,程序中某一类型的全体值,也不能简单的对应于数学上的集合,
例如,程序中的全体整数类型的值,并不对应于整数集,
因为返回整型值的function,取某些参数时,程序可能不会终止。
为了给这样的function和返回值找到指称,
我们给每个集合增加一个新的值:⊥
,读作bottom。
f(⊥) = ⊥
一个数学函数,如果参数是⊥
,那么结果就一定是⊥
,
这样的函数称为严格函数(strict function)。
反之,如果参数包含⊥
,但是结果不一定是⊥
,
这样的函数就称为非严格函数(non-strict function)。
如果程序语言中function的指称语义是非严格函数,
那么这样的语言,就称为非严格的语言(non-strict language)。
注:
严格性是指称语义中的概念,而求值是操作语义中的概念。
并且指称语义对于操作语义具有可靠性(soundness),
即,如果一个表达式根据操作语义求值为另一个,那么,它们必定具有相同的指称。
因此,对于non-strict language,求值策略可能并不唯一,
对Haskell来说,GHC是最流行的编译器,
它使用了惰性求值(lazy evaluation)。
在没有歧义的情况下,人们常用lazy暗指non-strict,
lazy更直观更有利于沟通。
引用透明性
An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program's behavior.
即,引用透明性(referential transparency)指的是,
程序中的表达式总是可以用它的值来代替。
而程序中的纯函数(pure function),指的是,
(1)这个函数对相同参数总是输出相同结果
(2)这个函数没有副作用(side effect)
因此,纯函数具有引用透明性。
此外,语言采用了惰性求值,意味着表达式的求值方式是按需确定的,
所以,依靠副作用来得到计算结果就不可行了,
我们不知道计算在什么时候发生。
因此,一旦语言拥抱了惰性求值,就不得不保证引用透明性,
反之则不一定,具有引用透明性的语言,可能不必是惰性求值的。
至于,纯函数是不是解决问题的最佳方式,目前尚无定论,
但至少它是为了追求优雅性,对通常编程方式的一种挑战。
历史上的纯函数式惰性语言
在20世纪70年代末,Gerry Sussman和Guy Steele发明了Scheme,它是Lisp的一个方言,与lambda演算很相似,并支持了词法作用域。
几乎同时,Robin Milner为了进行自动定理证明发明了ML,其中使用了多态类型系统。
但是Scheme和ML都是基于严格语义的语言(strict language)。
到了80年代,人们燃起了对非严格语义的(non-strict),或者说是按需求值的(call-by-need)函数式语言的研究热情。
这方面的研究理所当然的吸引了很多人,首先,函数式语言简单而优雅,其次,惰性(lazy)与引用透明性有关,并且还可以处理无穷长的数据结构(infinite data structure)。
在80年代中期,很多研究者都想设计实现一个纯函数式的(pure)惰性语言,Miranda和Lazy ML是其中的两个例子。
1987年波特兰FPCA'87会议之后,与会者想发明一种尚未命名的新语言,
其实一开始,人们想从一门现有的语言开始,逐渐发展迭代,比如说,使用当时最成熟的Miranda。
Miranda是David Turner的公司Research Software Limited开发的一门语言,它是惰性求值的,包含代数数据类型,使用了Hindley-Milner类型系统,从1985年起用于商业中。Miranda有很好用户接口,对实现的支持良好,还有大量的教材。
可是,与David Turner沟通后,他拒绝了这件事。
我们想要一门用于研究语言特性的语言,因此我们决定任何人都可以扩展和修改语言本身,重新实现或者发行。但是David Turner想维持一份语言规范,让语言具有最好的可移植性,他不想让Miranda出现各种不同的方言。
于是,Haskell的故事就这样开始了。
参考
A History of Haskell
Non-strict semantics
Lazy Evaluation of Haskell
Foundations for Programming Languages