Reputation: 1467
I wonder what is the reasoning behind the following behaviour?
@ 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
requires each of its subtypes to be a subtype of Bar
so why instance of Foo
is not a proper instance of Bar
?
@Edit
Just to make the question clearer: I wonder why it works like that. Some possible answers are:
Foo
which is not the instance of type Bar
in runtime)It seems that at least 1) is somewhat true (hiding subtyping as implementation detail).
Upvotes: 4
Views: 489
Reputation: 48400
Back in 12-Mar-2006 Scala 2.0 introduced new keyword
requires
which was used to represent self types
class C requires T extends B { ... }
requires
keyword was eventually deprecated on 27-Jul-2007 in Scala 2.6.0 in favour of the current self type syntax
The
requires
clause has been deprecated; use{ self: T =>; ... }
instead
however it serves as a good indicator of the design intention of the self type which was to model relationship of "Bar requires Foo" as opposed to "Bar is Foo", as per Daniel
Now, as to what is the difference between a self type and extending a trait, that is simple. If you say B extends A, then B is an A. When you use self-types, B requires an A.
For example, programer requires coffee, but that does not necessarily mean programer is coffee (although I would not be surprised if few lost souls managed the transition).
Furthermore, bug issue Can't access public members despite bounded 'this' #9718, which is similar to OP, was closed with @retronym (a compiler contributor) stating
The current behaviour is as specified and actually a feature: the self type is an implementation detail of
Bar
that should not be visible to clients.
Upvotes: 5
Reputation: 51648
AFAIU,
Foo
requires each of its subtypes to be a subtype ofBar
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
.
Upvotes: 3
Reputation: 4577
Because a selftype is an implementation detail of the Foo
trait. You may want to implement Foo
's methods in terms of methods that you inherit from Bar
, but not expose that fact to the users of your API.
Upvotes: 6