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

kir*_*uku 44 haskell scala type-inference

Haskell的类型推理引擎比Scala强大得多.在Haskell中,我很少需要显式地编写类型,而在Scala中,类型只能在表达式中推断,而不能在方法定义中推断.

例如,请参阅以下Haskell代码段:

size xs = loop xs 0
  where
    loop [] acc = acc
    loop (_ : xs) acc = loop xs (acc+1)
Run Code Online (Sandbox Code Playgroud)

它返回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)
}
Run Code Online (Sandbox Code Playgroud)

或者使用方法定义:

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)
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:为什么我不能像下面这样写它们?

def size = xs => {
  def loop = {
    case (Nil, acc) => acc
    case (_ :: xs, acc) => loop(xs, acc+1)
  }
  loop(xs, 0)
}
Run Code Online (Sandbox Code Playgroud)

再次使用方法定义:

def size(xs) = {
  def loop(xs, acc) = xs match {
    case Nil => acc
    case _ :: xs => loop(xs, acc+1)
  }
  loop(xs, 0)
}
Run Code Online (Sandbox Code Playgroud)

是因为没有人实现它吗?Scala的类型系统不是像这种情况所需的那么强大吗?还是有其他原因吗?

ham*_*mar 54

主要原因是Scala的类型系统允许子类型,这是Hindley-Milner类型推理算法不支持的.

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

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

  • 你几乎可以说Haskell的"功能不那么强大"因为它没有处理亚型多态性*BUT*在实际意义上,"power"=>语言表达式的#可以正确地进行类型推断.我们大多数人希望Scala的类型推断处理与Haskell一样多的语言. (9认同)
  • 那么O'Caml如何在存在强类型推断的情况下取消继承?你最终必须明确指定很多类型吗?(我不知道,因为我从未发现自己想要在ML中使用类.) (6认同)
  • @Nate:OCaml是一种结构类型的语言.因此,变量的类型是它支持的操作集.查看[这些](http://www.cs.cmu.edu/~neelk/rows.pdf)关于该主题的优秀幻灯片. (6认同)
  • @jsuereth:嗯,其他一切都相同,缺少子类型*会使它比提供它的类型系统更不强大.它也比像Agda这样的定理证明器的类型系统更不强大.这就是通过避免棘手的功能(边际利益很小),你可以更好地利用它来使你的工作变得易于使用. (2认同)

Kip*_*ros 24

我想已经给出了主要原因,但我发现Scala的创建者Martin Odersky引用的这句话特别有用,

Scala没有Hindley/Milner类型推断的原因是很难将其与诸如重载(ad-hoc变体,而不是类型类),记录选择和子类型等功能结合起来.我不是说不可能 - 存在许多包含这些功能的扩展; 事实上,我自己也是其中一些人.我只是说在实践中很难使这项工作很好,需要有小型表达式和良好的错误信息.这也不是一个关闭案例 - 许多研究人员正在努力推动这里的界限(例如在Remy的MLF中查看).但是现在,它是更好的类型推理与更好地支持这些功能的权衡.你可以两种方式进行权衡.我们想要与Java集成的事实倾向于支持子类型和远离Hindley/Milner.

来源:发表评论通用类型推断是一件坏事.


Owe*_*wen 19

哈马尔给出了最大的理由.这是另外两个:

  • Scala使用类型来解析名称

考虑

def foo(x) = x.a + x.b
Run Code Online (Sandbox Code Playgroud)

Scala怎么可能推断出论证的类型?它应该寻找与每个类ab领域?如果超过1怎么办?在哈斯克尔

foo x = (a x) + (b x)
Run Code Online (Sandbox Code Playgroud)

记录名称是唯一的,它提出了自己的问题,但意味着您可以随时推断出所引用的记录类型.

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

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

  • @Adam:Haskell记录不像Scala的类那样提供命名空间.它们位于外部命名空间中. (5认同)
  • @Adam:使用模块系统,您可以重复使用相同的字段名称,但如果一次有多个字段名称,则必须显式限定它们,例如`Foo.x`和`Bar.x`.不过,这仍然是一个轻微的烦恼. (3认同)
  • 哇,那么只有在我的系统中输入的内容才能有“.x”字段?看起来,这确实会让类型推断变得更容易(并且编码更困难?)。 (2认同)
  • @Adam Rabung:请记住,到目前为止,它并不是Haskell中访问数据的最常用方式.这是一个偶然的烦恼,但通常无关紧要,因为许多数据类型甚至没有首先命名字段. (2认同)

Lan*_*dei 13

另一个与Hindley-Milner类型推断不相符的方法方法重载,以及默认参数和varargs等相关功能.这就是为什么在Haskell中写一些像zipWithN这么难的原因(这在Scala中是微不足道的).