为什么Scala的类型推断不像Haskell那样强大?

时间:2011-08-29 18:17:12

标签: scala haskell type-inference

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的类型系统不是像这种情况所需的那么强大吗?还是有其他原因吗?

4 个答案:

答案 0 :(得分:54)

主要原因是Scala的类型系统允许子类型,Hindley-Milner type inference algorithm不支持。

Haskell没有子类型,所以算法在那里运行得更好,尽管GHC支持的许多流行类型系统扩展导致类型推断再次失败,迫使你为某些表达式提供显式类型签名。

最后,它是类型系统的力量和可以完成的类型推断量之间的权衡。 Scala和Haskell只是做出了不同的权衡。

答案 1 :(得分:24)

我想已经给出了主要原因,但我发现Scala的创作者Martin Odersky的这句话引起了特别丰富的信息,

  

Scala没有Hindley / Milner类型推断的原因是   很难与超载等功能结合使用   ad-hoc变体,而不是类型类),记录选择和子类型。   我不是说不可能 - 有许多扩展   结合这些功能;事实上,我一直对他们中的一些人有所了解   我。我只是说要很好地完成这项工作是非常困难的   练习,其中一个人需要有小型表达,而且好   错误消息。这也不是一个关闭案例 - 很多研究人员都是   努力推动这里的界限(例如在雷米看看   MLF)。但是现在它是更好的类型推理对比的权衡   更好地支持这些功能。你可以做出权衡   方法。我们想要与Java集成的事实倾向于扩展   支持亚型和远离欣德利/米尔纳。

来源:帖子Universal Type Inference is a Bad Thing下的评论。

答案 2 :(得分:19)

哈马尔给出了最大的理由。以下是另外两个:

  • Scala使用类型来解析名称

考虑

def foo(x) = x.a + x.b

Scala怎么可能推断出参数的类型?它应该查找具有ab字段的每个班级吗?如果超过1怎么办?在Haskell

foo x = (a x) + (b x)

记录名称是唯一的,它提出了自己的问题,但意味着您可以随时推断出哪种记录被引用。

  • 对于您的示例:Scala中的case表达式可以是异构的

在Scala中,匹配对象的类型可以用作匹配的一部分,也可以决定应该如何进行匹配。因此,即使case中的所有构造函数都用于List,您可能想要将除列表之外的内容传递给它,并使其失败。

答案 3 :(得分:13)

另一个与Hindley-Milner类型推断不相符的是method overloading,以及默认参数和varargs等相关功能。这就是为什么在Haskell中编写像zipWithN这样的东西这么难的原因(在Scala中这很简单)。