Why scala self type is not a subtype of its requirement

472 Views Asked by At

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:

  1. There is feature X that would not be possible with subtyping relation between those.
  2. It's not true that the subtyping relation occurs (e.g. exists such instance of type Foo which is not the instance of type Bar in runtime)
  3. Both scenarios (with and without subtyping relation) are valid and so the compiler team had to choose one of them. If so, was there a reason to make such a decision or it was a random choice?

It seems that at least 1) is somewhat true (hiding subtyping as implementation detail).

3

There are 3 best solutions below

0
On

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.

2
On

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.

0
On

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.