为什么Scala的self type不是其要求类型的子类型?

4

我想知道以下行为背后的原因是什么?

@ trait Bar
defined trait Bar

@ trait Foo { self: Bar => }
defined trait Foo

@ def x: Foo = ???
defined function x

@ val y: Bar = x
cmd3.sc:1: type mismatch;
 found   : ammonite.$sess.cmd1.Foo
 required: ammonite.$sess.cmd0.Bar
val y: Bar = x
             ^
Compilation Failed

AFAIU,Foo要求其每个子类型都是Bar的子类型,那么为什么Foo的实例不是Bar的适当实例?
@Edit
为了使问题更清楚:我想知道为什么会这样。可能的答案有:
1.有某个功能X在这些之间的子类型关系下是不可能的。 2.并不存在运行时不属于类型BarFoo类型的实例。 3.这两种情况(有和没有子类型关系)都是有效的,所以编译器团队不得不选择其中之一。如果是这样,是否有理由做出这样的决定,还是这是一个随机选择?
看起来,至少1)有点正确(将子类型隐藏为实现细节)。

2
“Foo”没有一个实例不是“Bar”的实例这一说法是正确的。但在 sealed trait Foo; object A extends Foo with Bar; object B extends Foo with Bar中,你也会认为“Foo”必须是“Bar”的子类型吗? - Jasper-M
@Jasper-M 非常好的观点,我可能不想要那个。然而,如果编译器提供这样的信息,这些信息可能非常有用。 - Krever
3个回答

6

由于自类型是Foo特质的实现细节,因此您可能希望根据从Bar继承的方法来实现Foo的方法,但不向API用户公开这一事实。


4

回到2006年3月12日,Scala 2.0引入了新的关键字。

requires

这被用来表示自身类型

class C requires T extends B { ... }

requires关键字在Scala 2.6.0中已于2007年7月27日弃用,采用现有的self type语法代替

requires子句已被弃用;使用{ self: T =>; ... }代替。

然而,它作为self类型设计意图的良好指标,旨在建模“Bar需要Foo”的关系,而不是“Bar是Foo”,参见Daniel

现在,自类型和扩展trait之间的区别很简单。如果您说B扩展A,则B是A。当您使用自类型时,B需要A。

例如,程序员需要咖啡,但这并不一定意味着程序员咖啡(尽管我不会惊讶如果有几个迷失的灵魂管理了这个转换)。

此外,类似于 OP 的 bug 问题 无法访问公共成员,尽管有“this”限制 #9718 已经被关闭,@retronym(编译器贡献者)表示:
“当前行为是按规定的,实际上是一个特性:self 类型是 Bar实现细节,不应该对客户端可见。”

3
我理解为,Foo 要求它的每个子类型都是 Bar 的子类型。

不是这样的。

def test[T <: Foo]: Unit = {
  implicitly[T <:< Bar] // doesn't compile
}

我定义了一个Foo的子类型,即T,它不是Bar的子类型。对于子类也是如此。
class Impl extends Foo with Bar

如果一个类扩展了Foo,那么它也必须扩展Bar
类型和类是不同的。子类型和子类也是不同的。子类型化和继承也是不同的。
在...
trait Foo { self: Bar => }

Foo不是Bar的子类型。因此,您不能将类型为Foo的值分配给类型为Bar的变量。

def x: Foo = ???
val y: Bar = x // doesn't compile

如果你想让Foo成为Bar的子类型,你应该使用继承。
trait Foo extends Bar
def x: Foo = ???
val y: Bar = x // compiles

或子类型化
type Foo <: Bar
def x: Foo = ???
val y: Bar = x // compiles

例如,使用self-types,您可以定义循环依赖关系:
trait Bar { self: Foo => } 
trait Foo { self: Bar => } 
class Impl extends Foo with Bar

如果trait A { self: B => }意味着A <: B,那么在这种情况下,我们将拥有Bar <: FooFoo <: Bar,因此Bar =:= Foo,但事实并非如此,这些特质的类型是不同的。 trait A { self: B => }可能意味着A <: B(在这种情况下,我们将没有循环依赖项或这些特质将具有相等的类型),但这并非必要:如果您需要A <: B,则可以声明A extends B,而trait A { self: B => }具有不同的含义:所有A的子类(而不是子类型)都是B的子类型。
或者您可以限制实现。
trait Foo { self: Impl => } 
class Impl extends Foo

Impl可以是特质Foo的唯一实现,就像将Foo与唯一的继承者sealed一样)但是没有必要使FooImpl的类型相同。

让我们再考虑以下示例

trait NatHelper { 
  //some helper methods 
} 

sealed trait Nat { self: NatHelper => 
  type Add[M <: Nat] <: Nat 
} 

object Zero extends Nat with NatHelper { 
  override type Add[M <: Nat] = M 
} 

class Succ[N <: Nat] extends Nat with NatHelper { 
  override type Add[M <: Nat] = Succ[N#Add[M]] 
}

请注意,抽象类型 Nat#Add[M]Nat 的子类型,但不需要将其作为 NatHelper 的子类型。
类型不一定与运行时内容相关联。类型可以具有独立的含义。例如,它们可以用于类型级编程,当您用类型来表达业务逻辑时。
此外,还有所谓的标记类型(或幻影类型),当您将一些信息附加到类型时。
val x: Int with Foo = 1.asInstanceOf[Int with Foo]

这里我们将"信息" Foo 附加到数字 1 上。在运行时,它仍然是相同的数字 1,但在编译时,它被丰富了 "信息" Foo。然后 x.isInstanceOf[Bar] 返回 false。我不确定你是否接受此示例,因为我们使用了 asInstanceOf,但重点是您可以使用某些库函数。

val x: Int with Foo = 1.attach[Foo] 

而你将不会知道它在幕后使用了asInstanceOf(常常发生的情况),你只会信任它返回了Int with Foo的签名。


评论不适合进行长时间的讨论;此对话已被移至聊天室 - Samuel Liew
有关类型和集合之间差异的更多信息:https://cs.stackexchange.com/questions/91330/what-exactly-is-the-semantic-difference-between-set-and-type https://math.stackexchange.com/questions/489369/difference-between-a-type-and-a-set https://planetmath.org/11typetheoryversussettheory - Dmytro Mitin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接