根据TAPL,§23.2:
参数多态性(...),允许单片
要“一般”键入的代码,使用变量代替实际类型,以及
然后根据需要用特定类型实例化。参数定义
是统一的:它们的所有实例的行为都相同。 (...)
Ad-hoc 多态性,相比之下,允许多态值显示
在不同类型“查看”时的不同行为。最普遍的
ad-hoc 多态性的例子是重载,它将单个
具有许多实现的功能符号;编译器(或运行时系统,取决于重载决议是静态的还是动态的)为每个应用程序选择适当的实现
函数,基于参数的类型。
因此,如果您考虑历史的连续阶段,非泛型官方 Java(又名 pre-J2SE 5.0,2004 年 9 月之前)具有临时多态性 - 所以你可以 overload a method - 但不是参数多态性,所以你不能write a generic method。之后你当然可以两者都做。
相比之下,从一开始in 1990,Haskell 就是参数多态的,这意味着你可以这样写:
swap :: (A; B) -> (B; A)
swap (x; y) = (y; x)
其中 A 和 B 是类型变量,可以将其实例化为所有类型,无需假设。
但没有预先存在的构造提供 ad-hoc 多态性,它旨在让您编写适用于几个但并非全部的函数> 类型。类型类被实现为实现这一目标的一种方式。
它们让您描述一个类(类似于Java 接口的东西),给出您希望为您的泛型类型实现的函数的类型签名。然后你可以注册一些(希望是几个)实例匹配这个类。同时,您可以编写一个泛型方法,例如:
between :: (Ord a) a -> a -> a -> Bool
between x y z = x ≤ y ^ y ≤ z
其中Ord 是定义函数(_ ≤ _) 的类。使用时,(between "abc" "d" "ghi") 被静态解析 为字符串(而不是整数)选择正确的实例 - 恰好在(Java 的)方法重载时。
您可以使用bounded wildcards 在 Java 中执行类似的操作。但是Haskell 和 Java 在这方面的主要区别在于只有 Haskell 可以自动进行字典传递:在这两种语言中,给定 Ord T 的两个实例,比如 b0 和 b1,你可以构建一个函数f,将这些作为参数并使用字典顺序生成对类型(b0, b1) 的实例。现在说你得到了(("hello", 2), ((3, "hi"), 5))。在 Java 中,您必须记住 string 和 int 的实例,并传递正确的实例(由 f 的四个应用程序组成!)以便将 between 应用于该对象。 Haskell 可以应用compositionality,并找出如何在只给定地面实例和f 构造函数的情况下构建正确的实例(当然,这扩展到其他构造函数)。
现在,就类型推断而言(这应该是一个不同的问题),对于两种语言来说,它都是不完整的,因为你总是可以编写一个编译器无法确定其类型的未注释程序。
对于 Haskell,这是因为它具有 impredicative(又名一流)多态性,对于这种多态性,类型推断是无法确定的。请注意,在这一点上,Java 仅限于一阶多态(Scala 扩展的东西)。
对于Java,这是因为它支持contravariant subtyping。
但这些语言的主要区别在于在实践中应用类型推断的程序语句范围,以及对类型推断结果正确性的重要性。
-
对于 Haskell,推理适用于所有“非高度多态”的术语,并根据已发布的著名算法扩展做出认真的努力来返回正确的结果:
-
对于 Java,类型推断以更有限的方式应用无论如何:
在 Java 5 发布之前,Java 中没有类型推断。根据 Java 语言文化,每个变量、方法和动态分配的对象的类型都必须由程序员显式声明。当 Java 5 中引入泛型(按类型参数化的类和方法)时,该语言保留了对变量、方法和分配的这一要求。但是多态方法(按类型参数化)的引入要求(i)程序员在每个多态方法调用站点提供方法类型参数或(ii)语言支持方法类型参数的推断。为了避免给程序员带来额外的文书负担,Java 5 的设计者选择执行类型推断来确定用于多态方法调用的类型参数。 (source,强调我的)
inference algorithm 本质上是that of GJ,但在事后添加了一个somewhat kludgy 通配符(请注意,我没有及时了解J2SE 6.0 中可能进行的更正)。方法的巨大概念差异在于 Java 的推理是 local,即表达式的推断类型仅取决于从类型系统生成的约束及其子表达式的类型,但不在上下文中。
请注意,关于不完整且有时不正确的类型推断的党派路线相对滞后。根据the spec:
还要注意,类型推断不会以任何方式影响可靠性。如果推断的类型是无意义的,则调用将产生类型错误。类型推断算法应该被视为一种启发式算法,旨在在实践中表现良好。如果它无法推断出所需的结果,则可以使用显式类型参数。