为什么自我注释的特质不能分配给自身类型?

4
trait Base

trait Plugin { base: Base =>
    def asBase: Base & Plugin = this
}

class Mix extends Base, Plugin

val plug: Plugin = new Mix
val baseA: Base= plug.asBase
val baseB: Base = plug // snorts with "Found: Plugin. Required: Base

为什么呢? 如果我没错的话,里氏替换原则被遵守了,因为所有的Plugin实例都是一种混合类型,包括Base的子类型。因此,可以用Plugin类型的对象代替Base类型的对象,而不会影响程序的正确性。


Scastie 重现问题:https://scastie.scala-lang.org/BalmungSan/D0ZAd9EIQ1KIUxn52IGl3Q/2 - Luis Miguel Mejía Suárez
1个回答

5

里氏替换原则说

https://en.wikipedia.org/wiki/Liskov_substitution_principle

子类型要求:假设 ϕ(x)是一个可以证明对于类型为 T的对象x成立的属性,那么对于类型为S的对象yϕ(y)应该是成立的,其中ST的一个子类型。
前置条件不能在子类型中被加强。
后置条件不能在子类型中被削弱。
不变式必须在子类型中被保留。
Liskov原则是关于子类型化的。因为Plugin不是Base的子类型,所以Liskov原则现在不适用。
implicitly[Plugin <:< Base] // doesn't compile

(<:<<:https://dev59.com/LPfvpYgB1922wOYJJIc_#75762663 但这对我们来说没问题;如果 PluginBase 的子类型,即 Plugin <: Base,那么还会有 implicitly[Plugin <:< Base] )。

我猜你可能把 trait Plugin { base: Base => }trait Plugin extends Base 或者 子类(继承)和 子类型 搞混了。

Scala(和Java)中类和类型的区别是什么?

Type 和 Class 的区别是什么?

https://typelevel.org/blog/2017/02/13/more-types-than-classes.html

如何区分参数类型?

Scala 3中细化类型和匿名子类的区别是什么?

trait Plugin extends Base 意味着 PluginBase 的子类。特别地,这意味着 PluginBase 的子类型,因此所有 Plugin 的子类型都是 Base 的子类型。 但是,trait Plugin { base: Base => } 意味着所有 子类 都必须是 Base 的子类,而不仅仅是 子类型 (此外,这并不意味着 PluginBase 的子类型)。实际上,很容易声明一种类型

type SubPlugin <: Plugin

这不是Base的子类型

implicitly[SubPlugin <:< Base] // doesn't compile

所有的Plugin实例都是一个混合类型,其中包括Base的子类型。
即使所有Plugin的值(OOP实例)都是Base的值(实例),这也不意味着PluginBase的子类型,也不意味着Plugin的所有类型都是Base的类型。例如,在val x: Plugin = ???中,x不是Base类型的术语。
你不应该将类型仅仅看作是类型的所有值的集合(特别是在类型级编程中,值根本不那么重要)。集合可以是类型的模型(在逻辑意义上:https://en.wikipedia.org/wiki/Model_theory),但集合和类型是不同的。

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

例如,如果我们定义了两个抽象类型。
type A

type B

那么这些类型就没有值。因此,从集合论的角度来看,这些集合是相等的(一个空集)。但是这些类型是不同的。在同伦类型理论(HoTT,https://en.wikipedia.org/wiki/Homotopy_type_theory)中,1 = 21 = 3是两种没有任何值但是不同的类型。

因此,可以用类型为Plugin的对象替换类型为Base的对象,而不会影响程序的正确性。

总有一些程序在运行时不会失败,但在编译时被拒绝。例如,if true then 1 else "a"在运行时不会失败,但是在没有子类型的语言(如Haskell)中可能会被拒绝(但Scala认为这是Any)。Scala拒绝val x: Int = if (true) 1 else "a",尽管这也不会在运行时失败。足够丰富的类型系统不能同时保证完备性和声音性。通常优先考虑声音性而不是完备性。

什么是一种好的编程语言?

https://math.stackexchange.com/questions/105575/what-is-the-difference-between-completeness-and-soundness-in-first-order-logic

你可能会问为什么 trait Plugin { base: Base => } 不总是意味着与 trait Plugin extends Base 相同。这是 Scala 类型系统创建者的设计决策。特别地,如果这样做相同,则两个不同的 traits trait A {this: B =>}trait B {this: A =>} 将具有相同的类型。
此外,在 trait Plugin extends Base 中,Base 必须是一个类/traits。但在 trait Plugin { base: Base => } 中,Base 可以是一个类型。 为什么 Scala 自类型不是其要求的子类型 自类型和 trait 子类之间的区别是什么? trait 继承和自类型注释之间的区别

哪种写法更符合 Scala 的习惯:trait TraitA extends TraitB 还是 trait TraitA { self: TraitB => }?


虽然我同意类和类型之间的区别非常重要,但我认为这是编译器的限制。除非我错过了一个可以获得既是“插件”又不是“基础”的示例,否则编译器应该能够生成合成子类型关系。 - Luis Miguel Mejía Suárez
@LuisMiguelMejíaSuárez 那么 trait Plugin { base: Base => }trait Plugin extends Base 之间的区别应该是什么呢?如果你已经有了 trait Plugin extends Base,为什么还需要 trait Plugin { base: Base => } 呢? - Dmytro Mitin
1
这取决于你提出问题的背景。如果你只关心子类型语义,那么你是正确的。但是,从建模/设计的角度来看,“self”类型的想法(在我看来)是让用户强制决定要混入哪个具体实现。正常的子类型并不一定能够提供这样的功能,至少没有这么好。例如,我通常会将混入“sealed”,因为我不希望用户能够添加新的实现,只需选择我提供的几个之一即可。 - Luis Miguel Mejía Suárez
@LuisMiguelMejíaSuárez 在继承的情况下,b4也无法编译,因为b4没有混入插件吗? https://scastie.scala-lang.org/DmytroMitin/OlQ6L1aIRROZm5WRfd1p7Q/1 - Dmytro Mitin
1
当然可以,但你可以自己覆盖它:https://scastie.scala-lang.org/BalmungSan/B5Fs4D9aSwiKDtAygEuLuA。我不希望用户能够这样做,而是选择我的插件实现之一。(记录一下,我在所有的生活中只做过一次这样的事情,这并不是一个常见的API设计。) - Luis Miguel Mejía Suárez
显示剩余4条评论

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