类型擦除是好的
让我们坚持事实
到目前为止,很多答案过分关注Twitter用户。这有助于专注于消息,而不是信使。有一个相当一致的消息,甚至只是上面提到的摘录:
It’s funny when Java users complain about type erasure, which is the only thing Java got right, while ignoring all the things it got wrong.
I get huge benefits (e.g. parametricity) and nil cost (alleged cost is a limit of imagination).
new T is a broken program. It is isomorphic to the claim “all propositions are true.” I am not big into this.
一个目标:合理的方案
这些tweets反映了一个观点,不关心我们是否可以让机器做某事,但更多的是,我们可以推断机器会做我们实际想要的东西。良好的推理是一个证明。证明可以用正式符号或不太正式的方式指定。不管规范语言如何,它们必须清晰和严格。非正式规范不是不可能正确结构,但在实际编程中往往有缺陷。我们最终得到补救,如自动化和探索性测试,以弥补我们与非正式推理的问题。这不是说测试本质上是一个坏主意,但引用的Twitter用户建议有一个更好的方法。
所以我们的目标是有一个正确的程序,我们可以明确和严格地对应于机器如何实际执行程序的理由。然而,这不是唯一的目标。我们还希望我们的逻辑具有一定程度的表达性。例如,只有这么多,我们可以表达与命题逻辑。从诸如一阶逻辑之类的通用(∀)和存在(∃)量化是很好的。
使用类型系统进行推理
这些目标可以很好地解决类型系统。这一点尤其明显,因为Curry-Howard correspondence.这种对应通常用下面的类比来表示:类型是程序作为定理是为了证明。
这种对应有些深刻。我们可以取逻辑表达式,并通过对应的类型来翻译它们。然后,如果我们有一个具有相同类型签名的程序编译,我们已经证明逻辑表达式是普遍真实的(重言式)。这是因为对应是双向的。类型/程序和定理/证明世界之间的转换是机械的,并且在许多情况下可以是自动的。
Curry-Howard很好地演示了我们想要如何处理程序的规范。
类型系统在Java中有用吗?
即使理解了库里 – 霍华德,有些人发现很容易忽略类型系统的价值,当它
>是非常难以使用
>对应于(通过Curry-Howard)到具有有限表现力的逻辑
>被破坏(其得到系统的表征为“弱”或“强”)。
关于第一点,也许IDE使Java的类型系统容易工作(这是高度主观的)。
关于第二点,Java碰巧几乎对应于一阶逻辑。泛型使用类型系统相当于通用量化。不幸的是,通配符只能为我们提供一小部分的存在量化。但通用量化是一个很好的开始。很高兴能够说List< A>的函数。为所有可能的列表普遍工作,因为A是完全不受约束。这导致Twitter用户正在谈论的“参数”。
一篇经常引用的关于参数的论文是Philip Wadler的Theorems for free!.本文的有趣之处在于,从单纯的类型签名,我们可以证明一些非常有趣的不变量。如果我们为这些不变式写入自动化测试,我们将非常浪费我们的时间。例如,对于List< A>,仅从类型签名用于展平
List flatten(List> nestedLists);
我们可以推论
flatten(nestedList.map(l -> l.map(any_function)))
≡ flatten(nestList).map(any_function)
这是一个简单的例子,你可以非正式地推理,但是当我们从类型系统中正式获得这样的证明并由编译器检查时,它甚至更好。
不擦除可以导致滥用
从语言实现的角度来看,Java的泛型(对应于通用类型)在用于获取我们的程序做什么的参数方面非常重要。这涉及到第三个问题。所有这些证明和正确性的获得需要一个没有缺陷的可靠的类型系统。 Java绝对有一些语言特性,使我们能够打破我们的推理。这些包括但不限于:
>与外部系统的副作用
>反射
非擦除泛型在许多方面与反射有关。没有擦除,我们可以使用设计我们的算法的实现携带的运行时信息。这意味着,静态地,当我们推理节目时,我们没有完整的画面。反思严重威胁我们关于静态的任何证据的正确性。这不是巧合反射也导致各种棘手的缺陷。
那么,非擦除泛型可能是什么“有用的”?让我们考虑tweet中提到的用法:
T broken { return new T(); }
如果T没有一个无参数构造函数会发生什么?在某些语言中,你得到的是null。或者也许你跳过空值,直接提高一个异常(null值似乎导致无论如何)。因为我们的语言是图灵完整的,所以不可能推断哪些调用会涉及“安全”类型,没有arg的构造函数,哪些不会。我们失去了我们的程序普遍工作的确定性。
擦除意味着我们推理(所以让我们擦除)
因此,如果我们想对我们的程序进行推理,强烈建议不要使用强烈威胁我们的推理的语言特性。一旦我们这样做,那么为什么不在运行时删除类型?他们不需要。我们可以获得一些效率和简单性,满意的是没有任何转型将失败或者方法可能在调用时丢失。
擦除鼓励推理。