问题 为什么类型推断对面向对象语言不切实际?


我正在研究一种新的编程语言的想法,理想情况下我希望这种语言能够混合一些功能和程序(面向对象)的概念。

我对Haskell这样的语言非常着迷的一个原因是它是静态类型的,但你不需要注释类型(魔术感谢Hindley-Milner!)。

对于我的语言,我真的很喜欢这个,但是在阅读了这个主题后,似乎大多数人认为类型推断对于子类型/面向对象是不切实际/不可能的,但是我不明白为什么会这样。我不知道F#,但我知道它使用Hindley-Milner并且是面向对象的。

我真的想要对此进行解释,并且最好是关于面向对象语言不可能进行类型推断的场景的示例。


1495
2018-03-20 09:26


起源

阅读提示:本杰明·皮尔斯,“类型和编程语言” - Ingo
您可能有兴趣阅读本文: research.microsoft.com/en-us/um/people/moskal/pdf/msc.pdf - SK-logic


答案:


当使用标称类型(这是一个打字系统,其中两个成员具有相同名称且相同类型的类不可互换)时,会有许多可能的类型,如下所示:

let f(obj) =
    obj.x + obj.y

任何同时拥有成员的类 x 和一个成员 y (支持的类型 + 运营商)将有资格作为可能的类型 obj 并且类型推断算法无法知道哪一个是你想要的那个。

在F#中,上面的代码需要一个类型注释。所以F#有面向对象和类型推断,但不能同时进行(局部类型推断除外)let myVar = expressionWhoseTypeIKNow),总是有效的)。


5
2018-03-20 09:41



我真的不明白为什么这是一个问题。在调用f之前,类型检查器不会知道obj的类型吗? - monoceres
@monoceres我在谈论类型检查的定义 f而不是呼叫站点。甚至可能没有电话 f 或致电 f 可能在不同的编译单元中。 - sepp2k
好的,但是如果我构建一个类型检查器来推断对象/函数/参数的类型/什么时候使用它们会怎样?在这种情况下,f可能有很多版本,具体取决于它的调用方式。这甚至是一个可能/理智的事情吗? - monoceres
@monoceres这意味着任何想要使用您的函数的人都需要访问您的源代码,源代码需要在每次使用时重新编译,并且不可能自己键入库(即您只能查看库)在使用库的代码的上下文中检查它。这些都有很多缺点,但它是C ++模板的工作原理,所以它并不是前所未有的。 - sepp2k
@ sepp2k C ++社区充分认识到这个模型有很多缺点,因此引入了模板类型的各种尝试(“概念”)。 - n.m.


添加到seppk的响应:对于结构对象类型,他描述的问题实际上消失了(f可以被赋予多态类型,如∀A&leq {x:Int,y:Int} .A→Int,或者甚至只是{x:Int ,y:Int}→Int)。但是,类型推断仍然存在问题。

根本原因在于:在没有子类型的语言中,键入规则强加 平等 对类型的约束。在类型检查期间处理这些非常好,因为通常可以使用类型的统一来立即简化它们。但是,通过子类型,这些约束被推广到 不等式 限制。你不能再使用统一,至少有三个不愉快的后果:

  1. 约束的数量和复杂性在组合上爆炸。
  2. 如果出现错误,您必须向用户显示的信息是不可理解的。
  3. 某些形式的量化很快就会变得不可判断。

因此,对于子类型的类型推断并非不可能(在90年代已经有很多关于这个主题的论文),但它不是很实用。

OCaml使用了一种更简单的替代方案,它使用所谓的方法 行多态性 代替子类型。这实际上是易处理的。


10
2018-03-20 12:37



有趣。不幸的是,我缺乏理论上的理解 打字 完全理解你的观点。关于使用C ++的其他答案中的讨论的任何想法,如模板概念(似乎最好描述为“编译时鸭子打字”)? - monoceres
从类型检查的角度来看,模板本质上只是宏。所以不是很有趣,也不是很模块化。 :) - Andreas Rossberg