语言:无类型和类型系统

3,257 阅读8分钟

1 无类型和类型系统

要严格地谈论类型系统及其属性,我们需要开始通过正式处理编程语言的一些更基本的方面。 特别是,我们需要清晰、精确和使用在数学上易于处理的工具,去表达和推理程序的语法和语义。

接下来开发的工具可以处理数字和布尔值,它可以作为几个基本概念的直接工具——抽象句法、归纳定义和运行时错误的溯源、评估和建模。

为以后更强大的语言(无类型语言)详细阐述相同的lambda演算故事,我们还必须处理名称绑定和替换,以展望未来。

1.1 无类型的λ运算

无类型的定义和一些基本属性或纯粹的 lambda演算, 对于大多数人来说,就是底层的“计算基础”,其余部分是描述的类型系统。

在 1960 年代中期,Peter Landin 观察到, 复杂的编程语言可以通过将其表述为微小的核心的微积分来理解语言的基本机制,

以及一系列方便的派生形式,其行为通过将其翻译成 core核心(Landin 1964、1965、1966;另见 Tennent 1981)。

Landin 使用的核心语言是 lambda 演算,这是一种新发明的形式系统在 1920 年代由 Alonzo Church (1936, 1941)提出,

其中所有计算都是简化为函数定义和应用的基本操作。

跟随兰丁的洞察力,以及约翰麦卡锡(1959, 1981)的开创性工作在,lambda 演算已广泛用于编程语言特性的规范,语言设计和实现,以及类型系统的研究之中。

它的重要性源于事实上,它可以同时被视为一种简单的编程语言,其中计算可以被描述为一个数学对象关于可以证明哪些严格陈述。

λ演算是众多核心演算中的一种,被用于类似以下目的:

	米尔纳、帕罗和沃克的 pi 演算(1992, 1991) 已成为定义语义的流行核心语言,
        基于消息的并发语言,
	Abadi 和 Cardelli 的对象计算(1996)提炼了面向对象语言的核心特征。 

大多数将lambda 演算作为开发的概念和技术可以相当直接地转移到其他语言。

这里的示例是纯无类型 lambda 演算的术语λ,或用布尔值和算术运算扩展的 lambda 演算λNB。 这相关的实现是完全无类型的。

1.2 运算的多个变化方式

这可以通过多种方式丰富 lambda 演算。

首先,它经常方便为数字、元组等功能添加特殊的具体语法,记录等,其行为已经可以用核心语言模拟。

更有趣的是,我们可以添加更复杂的功能,例如可变引用单元或非本地的异常处理,这些功能可以在核心中建模语言使用,否则只能通过使用相当繁重的转译的方式实现。

这种扩展甚至最终导致了诸如机器学习之类的语言(Gordon、Milner 和 Wadsworth,1979;Mil ner、Tofte 和 Harper,1990;Weis、Aponte、Laville、Mauny 和 Suárez,1989;Milner、Tofte、Harper 和 MacQueen,1997 年)、Haskell(Hudak 等人,1992 年),

或相关的计划(Sussman 和 Steele,1975 年;Kelsey、Clinger 和 Rees,1998 年)。 正如我们在现在中看到,对核心语言的扩展通常也涉及对类型系统的扩展。

1.3 更抽象的焦点,静态和动态

关于各种“纯类型λ演算”和变体之间的逻辑联系,两个都使用相似的概念、符号和技术,但在方向上有一些重要的差异。例如,对类型化 lambda 演算的研究。

通常关注的系统中,每一个类型良好的计算都是保证终止,而大多数编程语言都牺牲了这一点属性,以实现递归函数定义等功能。

上述定义中的另一个重要元素是它强调根据执行时将计算的值的属性对术语(句法短语)进行分类。

可以认为一个类型系统,作为计算运行时行为的一种静态近似程序中的条款。 (此外,分配给术语的类型通常是组合计算的,表达式的类型仅取决于其子表达式的类型。)

有时会显式添加“静态”一词——例如,我们说的是“静态类型编程语言”——以区分类别在编译时分析中,我们正在从诸如 Scheme 等语言中发现的动态或潜在类型中考虑(Sussman and Steele, 1975;Kelsey, Clinger, and Rees, 1998; Dybvig, 1996),其中运行时类型标签是用于区分堆中不同种类的结构。

像“动态类型”这样的术语可以说是用词不当,应该被替换为“动态检查”,但用法是标准的。

作为静态的,类型系统也必然是保守的:它们可以明确地证明不存在某些不良程序行为,但它们不能证明他们的存在,因此他们有时也必须拒绝程序实际上在运行时表现良好。

例如,像这样的程序

	if <complex test> then 5 else <type error>

将被拒绝为错误类型,即使碰巧 将总是评估为真,因为静态分析无法确定这是这样的。保守性和表现力之间的张力是类型系统设计中的一个基本事实。

允许更多的愿望要键入的程序——通过为它们的部分分配更准确的类型——是推动该领域研究的主要力量。

相关的一点是,相对简单的分析体现在大多数类型系统不能禁止任意不受欢迎的程序行为;他们只能保证类型良好的程序是免费的来自某些类型的不当行为。

例如,大多数类型系统可以检查静态地,原始算术运算的参数总是数字,方法调用中的接收者对象总是提供请求的方法等,但不是除法的第二个参数操作非零,或者数组访问总是在界限内。

可以通过给定语言中的类型系统消除的不良行为通常称为运行时类型错误。

记住这一点很重要这组行为是每种语言的选择:尽管在被认为是运行时类型错误的行为之间存在大量重叠不同的语言,原则上每个类型系统都有一个定义它旨在防止的行为。

每种类型的安全性(或健全性)必须根据自己的一组运行时错误来判断系统。

类型分析检测到的不良行为种类不限于低级错误,例如调用不存在的方法:类型系统也是用于强制执行更高级别的模块化属性并保护用户定义的抽象的完整性。

违反信息隐藏,例如因为直接访问其表示应该是抽象的数据值的字段,是运行时类型错误,其方式与以下完全相同:

    例如,将整数视为指针并使用它来使机器崩溃。

类型检查器通常内置在编译器或链接器中。这意味着他们必须能够自动完成工作,无需人工干预或与程序员的交互——即,它们必须体现计算上易于处理的分析。

但仍有很大的要求空间程序员的指导,以显式类型注释的形式程式。

2 小结

通常,这些注释的保持简单相当不容易,它们的存在以制作程序更容易书写和阅读。

但是,原则上,该程序符合一些任意规范可以编码在类型注释中;

在这在这种情况下,类型检查器将有效地成为一个证明检查器。比如使用扩展静态检查等技术(Detlefs、Leino、Nelson 和 Saxe,1998 年)

正在努力解决类型系统和全面的程序验证方法之间的这一领域,对一些实施全自动检查仅依赖于“轻度合理”的广泛类别的正确性属性程序注释来指导他们的工作。

甚至像 ML 这样广泛使用的类型系统(Damas 和 Milner,1982 年)可能在病理案例中表现出巨大的类型检查时间成本(Henglein和迈尔森,1991)。

有些语言具有无法确定的类型检查或类型重构问题,比如对于哪些算法是可在“大多数实际感兴趣的情况下”立刻停止。

出于同样的原因,我们最感兴趣的方法不仅仅是原则上是可自动化的,实际上也带有高效的算法用于检查类型。然而,究竟什么才是最有效的,仍然在辩论。

参考:

  皮尔斯 和特纳(Pierce and Turner),2000;
  纳达瑟和米勒(Nadathur and Miller),1988;
  普芬宁(Pfenning),1994