关于输入:Scala是否有像ML这样的值限制,如果不是,为什么呢?

Does Scala have a value restriction like ML, if not then why?

这是我对这个问题的看法。有人能证实、否认或详细说明吗?

我写道:

Scala doesn’t unify covariant List[A] with a GLB ? assigned to List[Int], bcz afaics in subtyping"biunification" the direction of assignment matters. Thus None must have type Option[⊥] (i.e. Option[Nothing]), ditto Nil type List[Nothing] which can’t accept assignment from an Option[Int] or List[Int] respectively. So the value restriction problem originates from directionless unification and global biunification was thought to be undecidable until the recent research linked above.

您可能希望查看上述评论的上下文。

ML的值限制将不允许在(以前被认为是罕见的,但可能更普遍的)情况下进行参数多态,否则这样做是合理的(即类型安全的),例如,特别是对于部分应用课程函数(这在函数式编程中很重要),因为可选的类型解决方案创建了一个功能和命令式编程之间的分层以及模块化抽象类型的中断封装。哈斯克尔有一个类似的双重单态限制。在某些情况下,OCAML可以放松限制。我详细阐述了其中的一些细节。

编辑:我在上面的引语中表达的原始直觉(值限制可以通过子类型消除)是不正确的。我的答案很好地阐明了这个问题,我无法决定在包含亚历克赛、安德烈亚斯或我的答案的集合中,哪一个是最好的答案。我觉得他们都是值得的。


正如我前面所解释的,当您将参数多态性与可变引用(或某些其他效果)结合起来时,就需要进行值限制(或类似的事情)。这完全独立于语言是否有类型推断,或者语言是否允许子类型。一个典型的反例

1
2
3
let r : ?A.Ref(List(A)) = ref [] in
r := ["boo"];
head(!r) + 1

不受删除类型批注的能力或向量化类型添加绑定的能力的影响。

因此,当您添加对f<:的引用时,您需要施加一个值限制,以避免失去可靠性。同样,MLSUB也无法摆脱价值限制。scala已经通过其语法强制了一个值限制,因为甚至没有办法编写一个具有多态类型的值的定义。


比那简单多了。在scala中,值不能有多态类型,只有方法可以。例如,如果你写

1
val id = x => x

它的类型不是[A] A => A

如果你采用多态的方法,例如

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

并尝试将其赋值

1
 val id1 = id

编译器将再次尝试(在本例中失败)推断特定的A,而不是创建多态值。

所以这个问题没有出现。

编辑:

如果你试图复制http://mlton.org/value restriction u替代品来代替scala中的"值限制"示例,你遇到的问题并不是缺乏letval与之完全对应。但你需要这样的东西

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

这是违法的。如果你写了def f[A]: A => A = ...,这是合法的,但是每次通话都会创建一个新的r。以毫升计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"

值限制允许的值。

也就是说,scala的规则等价于只允许lambda作为ML中的多态值,而不允许任何值限制。


编辑:此答案以前不正确。我已经完全重写了下面的解释,以便从安德烈亚斯和亚历克赛的回答下面的评论中获得新的理解。好的。

The edit history and the history of archives of this page at archive.is provides a recording of my previous mission and discussion.我选择编辑而不是删除并编写新答案的另一个原因是保留对此答案的评论。在我看来,这个答案仍然是需要的,因为尽管亚历克赛正确、最简洁地回答了主题,但安德烈亚斯的阐述对我获得理解是最有帮助的,但我认为外行读者可能需要一个不同的、更全面的(但希望仍然是生成的本质)解释,以便快速获得一些DEP。理解这个问题。另外,我认为其他的答案掩盖了一个整体性的解释是多么复杂,我希望天真的读者可以选择去品味它。我发现之前的解释并不是用英语描述所有细节,而是(数学家为了提高效率而做的)依靠读者从符号编程语言示例和前提域知识的细微差别中辨别细节(例如,关于编程语言设计的背景事实)。好的。

值限制出现在referenced1类型参数化对象2的突变处。在下面的MLTON代码示例中演示了在没有值限制的情况下会导致的类型不安全:好的。

1
2
3
4
5
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)

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

一种类型复杂的情况出现,以防止(重新)量化(即绑定或确定)所述引用(及其指向的对象)的类型参数(aka类型变量)到一个类型,当重用先前用不同类型量化的所述引用的实例时,该类型会有所不同。好的。

例如,连续函数应用程序(aka调用)重用此类引用的同一个实例时,就会出现这种情况(可以说是令人困惑和费解)。iow,每次应用函数时,引用的类型参数(与对象相关)都被(重新)量化的情况,但是相同的引用实例(及其指向的对象)被重用到函数的每个后续应用(和量化)中。好的。

相切地说,这些现象的出现有时是不直观的,因为缺乏明确的通用量词?(由于隐含的rank-1 prenex词法范围量化可以通过构造(如let或协程)从词法评估顺序中去除,并且可以说,当ml的值限制中可能出现不安全的情况时,更大的不规则性(与scala相比):好的。

安德烈亚斯写道:好的。

Unfortunately, ML does not usually make the quantifiers explicit in its syntax, only in its typing rules.

Ok.

例如,对于与数学符号类似的let表达式来说,重用引用的对象是需要的,它应该只创建和计算一次替换的实例化,即使它们在in子句中可能在词汇上被多次替换。因此,例如,如果函数应用程序在in子句中被评估为(无论是否在词法上),而每个应用程序的替换类型参数被重新量化(因为替换的实例化仅在词法上在函数应用程序中),那么类型安全性可以是los。如果应用程序没有全部强制只量化一次有问题的类型参数(即,不允许有问题的类型参数是多态的),则不会。好的。

值限制是ML的折衷,它可以防止所有不安全的情况,同时也可以防止一些(以前被认为是罕见的)安全的情况,从而简化类型系统。价值限制被认为是一个更好的折衷方案,因为早期(过时?)使用更复杂的类型方法的经验没有限制任何或多个安全案例,导致了命令式编程和纯函数式编程(又称应用程序)之间的分歧,并且泄漏了ML函数模块中抽象类型的一些封装。我引用了一些资料,并在这里详细阐述。不过,切题地说,我在思考早期反对分歧的论点是否真的能站起来反对这样一个事实:名字调用根本不需要值限制(例如,haskell-sique lazy evaluation,当它也被需要记忆时),因为概念上的部分应用程序不会在已经被评估的状态上形成闭包;以及-模块化组合推理需要名称,当与纯度结合时,模块化(范畴理论和等式推理)控制和效果组合。反对按名称调用的单变形限制参数实际上是关于强制类型注释的,但是当需要最佳记忆化(aka sharing)时,显式的说明就不那么麻烦了,因为模块化和可读性无论如何都需要所述注释。按值调用是一种精细的齿梳级别的控制,因此当我们需要低级别的控制时,也许我们应该接受值限制,因为更复杂的类型允许的罕见情况在命令式和应用式设置中会不太有用。但是,我不知道这两者是否可以以平滑/优雅的方式在相同的编程语言中分层/分离。代数效应可以在cbv语言(如ml)中实现,并且可以消除值限制。如果值限制影响到代码,可能是因为编程语言和库缺少处理效果的合适元模型。好的。

scala对所有这些引用都进行了语法限制,这是一种折衷,例如限制与ML的值限制相同甚至更多的情况(如果不限制的话是安全的),但在我们不会为与值限制相关的错误消息感到头疼的意义上,它更为规则。在scala中,我们永远不允许创建这样的引用。因此,在scala中,我们只能表示当引用的类型参数被量化时创建新引用实例的情况。注:OCAML在某些情况下放宽了值限制。好的。

注意,scala和ml都不能声明引用是不可变的1,尽管它们指向的对象可以用val声明为不可变的。注意,没有必要对不能改变的引用进行限制。好的。型

为了使复杂的类型出现,需要引用类型1的可变性的原因是,如果我们用一个非参数化的对象(即不是NoneNil4,而是例如Option[String]或EDOCX1〔4)来实例化引用(例如,let的substitutions子句)。,那么引用就没有多态类型(与它指向的对象相关),因此再量化问题就不会出现。因此,有问题的情况是,先实例化一个多态对象,然后在重新量化的上下文中分配一个新量化的对象(即改变引用类型),然后在随后的重新量化的上下文中取消引用(读取)引用(指向的对象)。如前所述,当重新量化的类型参数发生冲突时,会出现类型并发症,必须防止/限制不安全的情况。好的。型

啊!如果你不复习相关的例子就理解了这一点,我会印象深刻。好的。型

1imo改为使用短语"可变引用"而不是"被引用对象的可变性","引用类型的可变性"将更容易混淆,因为我们的目的是改变对象的值(及其类型),而该值由指针引用-而不是指指针的可变性。指的是什么。有些编程语言甚至不能明确区分什么时候不允许在基元类型的情况下改变引用或它们指向的对象。好的。型

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

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

4which are Noneand []respectively in ml.好的。型好啊。