Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a self annotated trait is not assignable to the type of self?

Tags:

scala

scala-3

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

Why? If I am correct, the Liskov substitution principle is complied because all instances of Plugin are of a concrete type that is a mix that include a subtype of Base. Therefore, objects of type Basecan be replaced with objects of type Plugin without affecting the correctness of the program.

like image 474
Readren Avatar asked Oct 28 '25 22:10

Readren


1 Answers

Liskov principle says

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

Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T.

Preconditions cannot be strengthened in the subtype.

Postconditions cannot be weakened in the subtype.

Invariants must be preserved in the subtype.

Liskov principle is about subtyping. Liskov principle is now not applicable because Plugin is not a subtype of Base

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

(<:< is weaker than <: https://stackoverflow.com/a/75762663/5249621 but this is ok for us; if Plugin were a subtype of Base i.e. Plugin <: Base then moreover implicitly[Plugin <:< Base] would be).

I guess you're confusing either trait Plugin { base: Base => } with trait Plugin extends Base or subclasses (inheritance) with subtypes

What is the difference between a class and a type in Scala (and Java)?

What is the difference between Type and Class?

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

How to distinguish parameteric types?

What is a difference between refinement type and anonymous subclass in Scala 3?

trait Plugin extends Base means that Plugin is a subclass of Base. In particular, this means that Plugin is a subtype of Base, so all subtypes of Plugin are subtypes of Base. But trait Plugin { base: Base => } means that all subclasses of Plugin must be subclasses of Base. This doesn't mean that all subtypes of Plugin must be subtypes of Base (moreover, this doesn't mean that Plugin is a subtype of Base). Indeed, it's easy to declare a type

type SubPlugin <: Plugin

which is not a subtype of Base

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

all instances of Plugin are of a concrete type that is a mix that include a subtype of Base.

Even if it's true that all values (OOP instances) of Plugin are values (instances) of Base this doesn't make Plugin a subtype of Base and this doesn't mean that all terms of type Plugin are terms of type Base. For example the term x in val x: Plugin = ??? is not a term of type Base.

You shouldn't think of a type as (only) a set of all values of the type (especially this becomes important in type-level programming where values are not so interesting at all). Sets can be a model (in logical sense: https://en.wikipedia.org/wiki/Model_theory) for types. But sets and types are different

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

For example if we define two abstract types

type A

type B

then there are no values of these types. So these sets would be equal in set-theoretic sense (an empty set). But these types are different. In homotopy type theory (HoTT, https://en.wikipedia.org/wiki/Homotopy_type_theory) 1 = 2 and 1 = 3 are two types without any values but two different types.

Therefore, objects of type Base can be replaced with objects of type Plugin without affecting the correctness of the program.

There are always programs that can't fail at runtime but that are rejected at compile time. For example if true then 1 else "a" can't fail at runtime but can be rejected by a language without subtyping e.g. Haskell (but not by Scala which thinks that this is Any). Scala rejects val x: Int = if (true) 1 else "a" although this can't fail at runtime either. Rich enough type system can't be sound and complete at the same time. Normally soundness is preferred over completeness

What is a sound programming language?

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

You may ask why wouldn't always trait Plugin { base: Base => } mean the same as trait Plugin extends Base. Well, this was design decision of Scala type-system creators. In particular, if this would be the same then two different traits trait A {this: B =>}, trait B {this: A =>} would have the same type.

Also in trait Plugin extends Base, Base must be a class/trait. But in trait Plugin { base: Base => }, Base can be a type.

Why scala self type is not a subtype of its requirement

What is the difference between self-types and trait subclasses?

Difference between trait inheritance and self type annotation

What is more Scala idiomatic: trait TraitA extends TraitB or trait TraitA { self: TraitB => }

like image 113
Dmytro Mitin Avatar answered Nov 01 '25 01:11

Dmytro Mitin