AFAIU, Foo
requires each of its subtypes to be a subtype of Bar
No it doesn't.
def test[T <: Foo]: Unit = {
implicitly[T <:< Bar] // doesn't compile
}
I defined a subtype of Foo
, namely T
, which is not a subtype of Bar
.
This is true for subclasses.
class Impl extends Foo with Bar
If a class extends Foo
it must extend Bar
too.
Types and classes are different. Subtypes and subclasses are different. Subtyping and inheritance are different.
In
trait Foo { self: Bar => }
Foo
is not a subtype of Bar
. So you can't assign value of type Foo
to variable of type Bar
def x: Foo = ???
val y: Bar = x // doesn't compile
If you want to make Foo
a subtype of Bar
you should use inheritance
trait Foo extends Bar
def x: Foo = ???
val y: Bar = x // compiles
or subtyping
type Foo <: Bar
def x: Foo = ???
val y: Bar = x // compiles
For example with self-types you can define cyclic dependencies:
trait Bar { self: Foo => }
trait Foo { self: Bar => }
class Impl extends Foo with Bar
If trait A { self: B => }
implied that A <: B
then we would have in such case that Bar <: Foo
and Foo <: Bar
, so Bar =:= Foo
but it's not true, types of those traits are different.
trait A { self: B => }
could mean that A <: B
(and in such case either we wouldn't have cyclic dependencies or such traits would have equal types) but there is no necessity in that: if you need A <: B
you can just declare A extends B
while trait A { self: B => }
has a different meaning: all subclasses (not subtypes) of A
are subtypes of B
.
Or you can limit implementation
trait Foo { self: Impl => }
class Impl extends Foo
(Impl
can be the only implementation of trait Foo
like making Foo
sealed
with the only inheritor) but there is no need to make types of Foo
and Impl
the same.
Let's consider also the following example
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]]
}
Notice that the abstract type Nat#Add[M]
is a subtype of Nat
but there is no need to make it a subtype of NatHelper
.
Types are not necessarily connected with runtime stuff. Types can have independent meaning. For example they can be used for type-level programming when you formulate your business logic in terms of types.
Also there are so called tagged types (or phantom types) when you attach some information to a type
val x: Int with Foo = 1.asInstanceOf[Int with Foo]
Here we attached "information" Foo
to number 1
. It's the same runtime number 1
but at compile time it's enriched with "information" Foo
. Then x.isInstanceOf[Bar]
gives false
. I'm not sure you'll accept this example since we use asInstanceOf
but the thing is that you can use some library function
val x: Int with Foo = 1.attach[Foo]
and you will not know that it uses asInstanceOf
under the hood (as often happens), you will just trust its signature that it returns Int with Foo
.
Foo
that is not an instance ofBar
. But would you also argue thatFoo
has to be a subtype ofBar
insealed trait Foo; object A extends Foo with Bar; object B extends Foo with Bar
? – Simitar