Scala是否有像ML这样的价值限制,如果没有,那么为什么?

时间:2018-02-03 06:50:23

标签: scala typing subtyping hindley-milner value-restriction

这是我对这个问题的看法。任何人都可以确认,否认或详细说明吗?

wrote

  

Scala没有统一协变 List[A]与GLB⊤分配 List[Int],bcz afaics在子类型“biunification”任务方向很重要。因此,None必须包含Option[⊥]类型(即Option[Nothing]),同上Nil类型List[Nothing],它无法接受来自的分配分别为Option[Int]List[Int]。因此,价值限制问题源于无方向统一,全球统一被认为是不可判定的,直到最近的研究结合起来。

您可能希望查看上述评论的the context

ML的值限制将禁止(formerly thought to be rare but maybe more prevalent)情况下的参数多态性,否则它将是合理的(即类型安全),例如特别是对于curried函数的部分应用(这在函数式编程中很重要) ,因为替代的打字解决方案在功能和命令式编程之间创建了一个分层,并打破了模块化抽象类型的封装。 Haskell具有类似的双单态限制。在某些情况下,OCaml放宽了限制。我elaborated了解其中一些细节。

编辑:我在上面的引文中表达的原始直觉(通过子类型可以避免价值限制)is incorrect。答案IMO很好地阐明了这个问题,我无法确定哪一个包含Alexey,Andreas'或我的,应该是最佳答案。 IMO他们都值得。

3 个答案:

答案 0 :(得分:2)

It's much simpler than that. In Scala values can't have polymorphic types, only methods can. E.g. if you write

val id = x => x

its type isn't [A] A => A.

And if you take a polymorphic method e.g.

 def id[A](x: A): A = x

and try to assign it to a value

 val id1 = id

again the compiler will try (and in this case fail) to infer a specific A instead of creating a polymorphic value.

So the issue doesn't arise.

EDIT:

If you try to reproduce the http://mlton.org/ValueRestriction#_alternatives_to_the_value_restriction example in Scala, the problem you run into isn't the lack of let: val corresponds to it perfectly well. But you'd need something like

val f[A]: A => A = {
  var r: Option[A] = None
  { x => ... }
}

which is illegal. If you write def f[A]: A => A = ... it's legal but creates a new r on each call. In ML terms it would be like

val f: unit -> ('a -> 'a) =
    fn () =>
       let
          val r: 'a option ref = ref NONE
       in
          fn x =>
          let
             val y = !r
             val () = r := SOME x
          in
             case y of
                NONE => x
              | SOME y => y
          end
       end

val _ = f () 13
val _ = f () "foo"

which is allowed by the value restriction.

That is, Scala's rules are equivalent to only allowing lambdas as polymorphic values in ML instead of everything value restriction allows.

答案 1 :(得分:2)

正如我之前解释过的,当您将参数多态性与可变引用(或某些其他效果)结合使用时,就会出现对值限制或类似内容的需求。这完全独立于语言是否具有类型推断,或者语言是否也允许子类型。像

这样的规范反例
let r : ∀A.Ref(List(A)) = ref [] in
r := ["boo"];
head(!r) + 1

不会受到忽略类型注释的能力的影响,也不会受到为量化类型添加绑定的能力的影响。

因此,当您添加对F<的引用时,您需要施加值限制以避免失去稳健性。同样,MLsub也无法摆脱价值限制。 Scala已经通过其语法强制执行值限制,因为甚至无法编写具有多态类型的值的定义。

答案 2 :(得分:1)

编辑:此答案为incorrect 之前。我完全重写了下面的解释,以便从Andreas和Alexey的答案中收集我的新理解。

archive.is中此页面的编辑历史和档案历史记录提供了我之前的误解和讨论的记录。我选择编辑而不是删除和编写新答案的另一个原因是保留对此答案的评论。国际海事组织,这个答案仍然需要,因为虽然阿列克谢正确地回答了主题标题并且最简洁 - 安德烈亚斯的阐述对我来说最有帮助的是获得理解 - 但我认为外行读者可能需要一个不同的,更全面的(但希望仍然如此)生成本质)解释,以便迅速获得对问题的一些深入理解。另外我认为其他答案模糊了整体解释的复杂程度,我希望天真的读者可以选择品尝它。我发现的先前阐述并未说明英语中的所有细节,而是(因为数学家倾向于效率)依赖于读者从符号编程语言示例和先决条件领域知识的细微差别中辨别细节(例如关于编程语言设计的背景知识。)

值限制出现在我们有引用 1 类型参数化对象 2 的变异的地方。在没有价值限制的情况下会导致类型不安全,请参见以下MLton code example

val r: 'a option ref = ref NONE
val r1: string option ref = r
val r2: int option ref = r
val () = r1 := SOME "foo"
val v: int = valOf (!r2)

NONE引用的对象中包含的null值(类似于r)可以分配给类型参数{{1}的任何具体类型的引用因为'a具有多态类型r。这将导致类型不安全,因为如上例所示,已分配给a'r的{​​{1}}引用的同一对象可以用{编写(即突变){ {1}}值通过string option ref引用,然后通过int option ref引用读取为string值。值限制会为上面的示例生成编译器错误。

出现打字复杂化以防止 3 所述参考(及其指向的对象)的类型参数(也称为类型变量)的(重新)量化(即绑定或确定)重新使用先前用不同类型量化的所述参考实例的类型。

这种(可以说是令人困惑和错综复杂的)案例出现for example,其中连续的函数应用程序(aka调用)重用这种引用的相同实例。 IOW,每次应用函数时,(重新)量化参考的类型参数(属于对象)的情况,引用的相同实例(以及它指向的对象) )重复用于函数的每个后续应用(和量化)。

切向地,这些的出现有时是non-intuitive due to lack of明确的通用量词∀(因为隐式rank-1 prenex lexical scope quantification可以通过诸如r1或coroutines之类的结构从词汇评估顺序中移除可以说更大的不规则性(与Scala相比),当ML的价值限制可能出现不安全的情况时:

Andreas wrote

  

不幸的是,ML通常不会在其语法中明确使用量词,只能在其输入规则中使用。

例如,let expressions which analogous to math notation需要重用引用的对象,只应创建和评估替换一次的实例化,即使它们可能词法替换在int条款中不止一次。所以for example,如果函数应用程序评估(无论是否也有词法),r2子句中的替换的类型参数被重新量化应用程序(因为替换的实例化只是在函数应用程序中 ),如果应用程序不是全部被强制仅量化有问题的类型参数一次(即不允许),则类型安全性可能会丢失违规类型参数是多态的。)

值限制是ML的妥协,以防止所有不安全的情况,同时还防止一些(formerly thought to be rare)安全案例,以简化类型系统。价值限制被认为是一个更好的折衷方案,因为早期(antiquated?)使用更复杂的打字方法,不限制任何或许多安全案例,导致bifurcation between imperative and pure functional (aka applicative) programming并泄露了一些封装ML仿函数模块中的抽象类型。我引用了一些资料并详细阐述了here。尽管如此,我正在思考early argument against分叉是否真的能够反对这样一个事实:对于名字来说,根本不需要价值限制(例如,当需要记忆时,Haskell式的懒惰评估)因为从概念上讲,部分应用程序不会在已经评估的状态上形成闭包;并且按名称is required for模块compositional reasoning进行调用,当与纯度结合时,模块化(category theoryequational reasoning)控制和效果组合。单变量限制argument against按名称调用really about强制类型注释,但是当需要最佳存储(也称为共享)时明确是明确的,因为模块化和可读性需要任何方式都需要注释。按值调用是一个精细的梳齿控制水平,所以在我们需要低级别控制的地方,那么也许我们应该接受价值限制,因为更复杂的打字所允许的罕见情况在{{3 }}。但是,我不知道这两个是否能够以平滑/优雅的方式在同一编程语言中进行分层/隔离。代数效应可以用诸如ML的CBV语言实现,并且它们可以消除值限制。 IOW,如果值限制侵犯了您的代码,可能是因为the imperative versus applicative setting

Scala针对your programming language and libraries lack a suitable metamodel for handling effects提出a syntactical restriction,这是一种限制all such references甚至更多情况(如果不受限制,那将是安全的)的折衷方案,而不是ML的价值限制,但更为规律从某种意义上说,我们不会对与价值限制有关的错误信息感到头疼。在Scala中,我们for example the same创建了这样的引用。因此,在Scala中,我们never allowed在量化类型参数时创建引用的新实例。请注意can only express cases中的值限制OCaml relaxes

注意afalak Scala和ML都没有启用声明引用是不可变的 1 ,尽管它们指向的对象可以用let声明为不可变的。请注意,不需要对无法变异的引用进行限制。

为了使复杂的输入案例出现,需要引用类型 1 的可变性的原因是因为如果我们实例化引用(例如在{{1的替换子句中) }})使用非参数化对象(即不是inin 4 ,而是例如vallet),然后引用some cases一个多态类型(与它指向的对象有关),因此重新量化问题永远不会出现。因此,有问题的情况是由于使用多态对象进行实例化,然后在重新量化的上下文中随后分配新量化的对象(即,使引用类型变异),然后在随后的(引用的对象)引用中解除引用(读取)重新量化的背景。如前所述,当重新量化的类型参数发生冲突时,会出现打字复杂情况,必须防止/限制不安全的情况。

唷!如果您在不查看链接示例的情况下理解这一点,我会留下深刻的印象。

1 IMO反而使用短语“mutable references”而不是“引用对象的可变性”和“引用类型的可变性”将更容易混淆,因为我们的意图是改变指针引用的对象的值(及其类型) - 不是指引用所指向的指针的可变性。一些编程语言won’t have改变了引用或它们指向的对象。

2 其中一个对象甚至可以是一个函数,在允许一流函数的编程语言中。

3 为了防止在运行时由于访问(读取或写入)引用对象而产生分段错误,并假定其静态(即在编译时)确定的类型是不是对象实际拥有的类型。

4 ML中分别为NoneNil