更具体地说:
1. 在Java中,类型推断算法与Haskell中的类型推断有何不同?
2. 请举一个例子,说明在Java / Scala中可以编写但在Haskell中无法编写的情况(根据这些平台的模块化特性),反之亦然。
因此,如果您考虑历史的连续阶段,非通用官方Java(也称为J2SE 5.0之前的版本,即2004年9月之前)具有特定的多态性 - 因此您可以重载方法 - 但没有参数化的多态性,因此您无法编写通用方法。当然,之后您都可以做到这两点。
相比之下,自1990年成立以来,Haskell就是参数化多态的,这意味着您可以编写:
swap :: (A; B) -> (B; A)
swap (x; y) = (y; x)
其中A和B是类型变量,可以实例化为所有类型,没有任何假设。
但是之前并没有提供给特定场景的多态性构造,这意味着您无法编写适用于多个,但不是所有类型的函数。类型类被实现为实现此目标的一种方式。
它们允许您描述一个类(类似于Java接口),给出要为通用类型实现的函数的类型签名。然后,您可以注册与此类匹配的一些(希望是多个)实例。同时,您可以编写一个通用方法,例如:
between :: (Ord a) a -> a -> a -> Bool
between x y z = x ≤ y ^ y ≤ z
Ord
是定义函数 (_ ≤ _)
的类。当被使用时,(between "abc" "d" "ghi")
被静态解析,以选择适用于字符串(而不是例如整数)的正确实例- 正好在(Java的)方法重载将要发生的那一刻。
在Java中,您可以使用有界通配符来实现类似的功能。但是Haskell和Java之间的关键区别在于,只有Haskell可以自动进行字典传递:在两种语言中,假设有两个Ord T
的实例,比如b0
和b1
,您可以构建一个函数f
,它以这些作为参数并生成对于成对类型(b0,b1)
的实例,例如使用词典顺序。现在假设您有((“hello”,2),((3,“hi”),5))
。在Java中,您必须记住string
和int
的实例,并传递正确的实例(由四个f
应用组成!)才能将between
应用于该对象。Haskell可以应用组合性,并找出如何仅通过基础实例和f
构造函数构建正确的实例(当然,这也适用于其他构造函数)。
现在,就类型推断而言(这可能应该是一个不同的问题),对于两种语言来说都是不完全的,因为您总是可以编写一个未注释的程序,编译器将无法确定其类型。
对于Haskell来说,这是因为它具有不可预测(也称为一级)多态性,对于这种类型推断是不可判定的。请注意,在这一点上,Java仅限于一阶多态性(Scala扩展了这一点)。
对于Java来说,这是因为它支持逆变子类型。
但是,这些语言主要在实践中的类型推断适用范围和给予类型推断结果正确性的重要性方面存在差异。
对于 Haskell,推断适用于所有“非高度多态”的术语,并且会认真努力基于已知算法的发布扩展返回可靠结果:A
和B
)只能实例化为非多态类型(我在简化,但这本质上是您可以在例如Ocaml中找到的ML风格的多态性)。推理算法本质上是GJ的算法,但是增加了一些有点 笨拙的通配符作为事后想法的补充(请注意,我不知道J2SE 6.0中可能进行的更正)。在方法论上的巨大差异在于Java的推理是局部的,也就是说,表达式的推断类型仅取决于从类型系统生成的约束和其子表达式的类型,而不取决于上下文。
请注意,关于不完整且有时不准确的类型推断的立场相对轻松。根据规范:还要注意,类型推断不会以任何方式影响音度。如果推断出来的类型是荒谬的,则调用将产生类型错误。应将类型推断算法视为一种启发式算法,旨在在实践中表现良好。如果它未能推断出所需的结果,则可以改用显式类型参数。
参数多态意味着,我们不关心类型,我们将为任何类型实现相同的函数。例如,在Haskell中:
length :: [a] -> Int
length [] = 0
length (x:xs) = 1 + length xs
我们不关心列表中元素的类型,只关心它们的数量。
临时多态(又名方法重载)意味着我们将根据参数的类型使用不同的实现方式。
下面是Haskell中的一个例子。假设我们想定义一个名为makeBreakfast
的函数。
如果输入参数是Eggs
,我希望makeBreakfast
返回如何制作鸡蛋的消息。
如果输入参数是Pancakes
,我希望makeBreakfast
返回如何制作煎饼的消息。
我们将创建一个名为BreakfastFood
的类型类,该类型类实现了makeBreakfast
函数。 makeBreakfast
的实现将根据输入类型的不同而有所不同。
class BreakfastFood food where
makeBreakfast :: food -> String
instance BreakfastFood Eggs where
makeBreakfast = "First crack 'em, then fry 'em"
instance BreakfastFood Toast where
makeBreakfast = "Put bread in the toaster until brown"
根据约翰·米切尔的《编程语言概念》,参数多态和重载(也称为特殊多态)之间的关键区别在于,参数多态函数使用一种算法来操作许多不同类型的参数,而重载函数可能会针对每种参数类型使用不同的算法。
关于参数多态和特定多态的完整讨论,以及它们在Haskell和Java中的可用程度,需要较长的篇幅;然而,您具体的问题可以更简单地解决:
Java中类型推断的算法与Haskell中的类型推断有何不同?
据我所知,Java不进行类型推断。因此,区别在于Haskell进行了类型推断。
请给我一个例子,说明某些情况下可以在Java / Scala中编写但无法在Haskell中编写(根据这些平台的模块化功能),反之亦然。
Haskell可以做到而Java无法做到的一个非常简单的例子是定义maxBound :: Bounded a => a
。我不了解足够的Java来指出Haskell无法做到的事情。
return null;
)。不过,在Scala中使用隐式参数是可能的。 - Rotsorclass BoundedInt implements Bounded {
private final int bound;
public BoundedInt(int bound) {
this.bound = bound;
}
public Integer maxBound() {
return bound;
}
} 抱歉格式有点乱。虽然冗长,但它似乎正是Haskell中Bounded所做的事情。
- xpmatteo
reverse :: [a] -> [a]
就是这样一种类型。而临时多态函数sort :: Ord a => [a] -> [a]
需要额外的信息,由类约束 Ord a 来指导。我不确定“临时”的词源,但后一种形式的多态性不是普通 lambda-calculus 的特征。 - vivianList<T> filter(Function<T>, List<T>)
完全不关心T
是什么。你的例子是参数多态性和子类型多态性的混合,这引起了协变和逆变。这与临时多态性是两回事。 - user545680Read a =>
这样的东西在脑海中翻译成类似于<? extends Read>
的内容(我不太确定,因为我已经很久没有写 Java 了)。 - fuz