Haskell的类型推理引擎比Scala的强大得多。在Haskell中,我很少需要显式地编写类型,而在Scala中,类型只能在表达式中推理,而不能在方法定义中推理。
例如,请参阅下列Haskell程式码片段:
size xs = loop xs 0
where
loop [] acc = acc
loop (_ : xs) acc = loop xs (acc+1)
它返回List的大小。Haskell编译器可以识别使用了什么类型以及函数定义是什么。等效的Scala代码:
def size[A]: List[A] => Int = xs => {
def loop: (List[A], Int) => Int = {
case (Nil, acc) => acc
case (_ :: xs, acc) => loop(xs, acc+1)
}
loop(xs, 0)
}
或者使用方法定义:
def size[A](xs: List[A]) = {
def loop(xs: List[A], acc: Int): Int = xs match {
case Nil => acc
case _ :: xs => loop(xs, acc+1)
}
loop(xs, 0)
}
我的问题是:为什么我不能像下面这样写呢?
def size = xs => {
def loop = {
case (Nil, acc) => acc
case (_ :: xs, acc) => loop(xs, acc+1)
}
loop(xs, 0)
}
再次使用方法定义:
def size(xs) = {
def loop(xs, acc) = xs match {
case Nil => acc
case _ :: xs => loop(xs, acc+1)
}
loop(xs, 0)
}
是因为还没有人实现它吗?是Scala的类型系统不够强大吗?还是有其他原因?
5条答案
按热度按时间kyks70gy1#
主要原因是Scala的类型系统允许子类型化,而Hindley-Milner类型推理算法不支持。
Haskell没有子类型化,因此算法在这里工作得更好,尽管GHC支持的许多流行类型系统扩展会导致类型推理再次失败,迫使您为一些表达式提供显式类型签名。
最后,这是类型系统的能力和类型推理的数量之间的权衡,Scala和Haskell只是做了不同的权衡。
bwitn5fc2#
我想主要的原因已经给出了,但是我发现Scala的创建者MartinOdersky的这句话信息量特别大,
Scala没有Hindley/米尔纳类型推理的原因是它很难与重载(ad-hoc变体,而不是类型类)、记录选择和子类型化等特性相合并。实际上,我自己对其中的一些非常熟悉,我只是说,在实际中很难很好地使用它,因为需要使用较小的类型表达式,和良好的错误信息。这也不是一个封闭的案例-许多研究人员正在努力突破这里的界限(看看雷米的MLF的例子)。但是现在,这是更好的类型推理和更好地支持这些特性之间的权衡。您可以在两种方式之间进行权衡。我们希望与Java集成的事实使天平倾向于子类型化,而远离Hindley/米尔纳。
来源:Universal Type Inference is a Bad Thing帖子下的评论。
r1zk6ea13#
哈马尔给出了最大的理由。下面是另外两个理由:
请考虑
Scala怎么可能推断参数的类型呢?它应该寻找每个有
a
和b
字段的类吗?如果有不止一个呢?在Haskell记录名称是唯一的,这会带来其自身的问题,但意味着您始终可以推断出所引用的记录类型。
case
表达式可以是异构的在Scala中,被匹配对象的类型既可以作为匹配的一部分,也可以用来决定如何进行匹配。因此,即使
case
中的所有构造函数都是针对List
的,你 * 可能 * 希望向它传递列表以外的东西,并让它失败。jhdbpxl94#
另一个不适合Hindley-Milner类型推理的地方是method overloading,以及相关的特性,比如默认参数和varargs,这就是为什么在Haskell中很难写zipWithN这样的东西(在Scala中很容易)。
bwntbbo35#
我已经阅读了上面的一些回应,然而,当你意识到F#自2006年以来在Hindley-Milner类型推断中包含了OOP的完整子类型时,一些这样的结论可能会受到怀疑。
1.增加了方法重载解析和对象表达式,以实现与.NET(2004)的互操作性
1.为对象编程添加类/接口结构(2005)
1.添加了“静态解析类型参数”,用于以符合Hindley-Milner类型推断的方式处理重载算术(2005)
1.在Hindley-Milner类型推断中增加了分型处理(2006)
源代码history of F#