This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Question on abstract types

8 replies
Christian Szegedy
Joined: 2009-02-08,
User offline. Last seen 42 years 45 weeks ago.

Hi, I've got a rather newbie question on type bounds for abstract types.

Consider the following example:

trait A
trait B
trait X { type T <: A }
trait Y { type T <: B }
trait Z extends X with Y

It fails to compile with the error message:

error overriding type T in trait X with bounds >: Nothing <: A; type
T in trait Y with bounds >: Nothing <: B has incompatible type
Y.this.T trait Z extends X with Y

However, if I define:
trait Z extends X with Y { type T <: A with B }
It compiles fine.

I am just curious: Is there any good (practical or theoretical)
argument for the compiler not to automatically infer the type bounds
for T?

One such argument would if there is some subtle difference between the
bound "A with B" and "B with A" which I don't know about, but would
make such an inference ambigous. However if I add the following
concrete example:

class C extends Z { class T extends B with A }

It still compiles fine, despite difference between "B with A" and "A with B".

Randall R Schulz
Joined: 2008-12-16,
User offline. Last seen 1 year 29 weeks ago.
Re: Question on abstract types

On Thursday March 19 2009, Christian Szegedy wrote:
> Hi, I've got a rather newbie question on type bounds for abstract
> types.
>
> Consider the following example:
>
> trait A
> trait B
> trait X { type T <: A }
> trait Y { type T <: B }
> trait Z extends X with Y
>
> It fails to compile with the error message:
>
> error overriding type T in trait X with bounds >: Nothing <: A; type
> T in trait Y with bounds >: Nothing <: B has incompatible type
> Y.this.T trait Z extends X with Y
>
> However, if I define:
> trait Z extends X with Y { type T <: A with B }
> It compiles fine.
>
> I am just curious: Is there any good (practical or theoretical)
> argument for the compiler not to automatically infer the type bounds
> for T?
>
> One such argument would if there is some subtle difference between
> the bound "A with B" and "B with A" which I don't know about,

The order of "with T" clauses is used to linearize the inheritance
structure when a class is defined with multiple mix-ins so that
super.someThing references inside the mix-in traits can be
disambiguated.

I have no idea whether this has anything to do with the behavior you're
seeing, though.

> ...

Randall Schulz

Christian Szegedy
Joined: 2009-02-08,
User offline. Last seen 42 years 45 weeks ago.
Re: Question on abstract types

Of course I know that. However, I don't know whether the order of with
clauses on the type bound is significant. (The example I gave suggests
that it does not seem to matter.)

On 3/19/09, Randall R Schulz wrote:
> On Thursday March 19 2009, Christian Szegedy wrote:
>
> The order of "with T" clauses is used to linearize the inheritance
> structure when a class is defined with multiple mix-ins so that
> super.someThing references inside the mix-in traits can be
> disambiguated.
>
> I have no idea whether this has anything to do with the behavior you're
> seeing, though.
>
>
> > ...
>
>
>
> Randall Schulz
>

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: Question on abstract types

On Thu, Mar 19, 2009 at 8:36 PM, Christian Szegedy
wrote:
> Hi, I've got a rather newbie question on type bounds for abstract types.
>
> Consider the following example:
>
> trait A
> trait B
> trait X { type T <: A  }
> trait Y {  type T <: B }
> trait Z extends X with Y
>
> It fails to compile with the error message:
>
> error overriding type T in trait X with bounds >: Nothing <: A;  type
> T in trait Y with bounds >: Nothing <: B has incompatible type
> Y.this.T trait Z extends X with Y
>
> However, if I define:
> trait Z extends X with Y {  type T <: A with B }
> It compiles fine.
>
> I am just curious: Is there any good (practical or theoretical)
> argument for the compiler not to automatically infer the type bounds
> for T?
>
> One such argument would if there is some subtle difference between the
> bound "A with B" and "B with A" which I don't know about, but would
> make such an inference ambigous. However if I add the following
> concrete example:
>
> class C extends Z {  class T extends B with A }
>
> It still compiles fine, despite difference between "B with A" and "A with B".
>
Currently, there is indeed a difference between A with B and B with A.
Their linearization differs, which can lead to different member
resolution. Here's how you can see this:

scala> trait A { type T; val m: T }
defined trait A

scala> trait B { type T <: String }
defined trait B

scala> val x: A with B = new A with B { type T = String; val m = "abc" }
x: A with B = $anon$1@1a896a4

scala> val s: String = x.m
s: String = abc

scala> val y: B with A = x
y: B with A = $anon$1@1a896a4

scala> val t: String = y.m
:8: error: type mismatch;
found : y.T
required: String
val t: String = y.m
^

So, the member `m' has a different type in `x' and `y'. In `x' of type
`A with B` it's `T <: String' whereas in `y' of type `B with A' it's
`T <: Any'. This is a bit unfortunate. We are currently playing with
the idea to treat type composition different from inheritance.
Inheritance would still define a linearization, but type composition
would be commutative.

Cheers

Jesper Nordenberg
Joined: 2008-12-27,
User offline. Last seen 42 years 45 weeks ago.
Re: Question on abstract types

martin odersky wrote:
> Currently, there is indeed a difference between A with B and B with A.
> Their linearization differs, which can lead to different member
> resolution. Here's how you can see this:
>
> scala> trait A { type T; val m: T }
> defined trait A
>
> scala> trait B { type T <: String }
> defined trait B
>
> scala> val x: A with B = new A with B { type T = String; val m = "abc" }
> x: A with B = $anon$1@1a896a4

Why is this type intersection even allowed? A#T and B#T are not related
in any way, so this is akin to structural typing. It's not allowed for
non-type members.

/Jesper Nordenberg

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: Re: Question on abstract types

On Fri, Mar 20, 2009 at 10:33 AM, Jesper Nordenberg wrote:
> martin odersky wrote:
>>
>> Currently, there is indeed a difference between A with B and B with A.
>> Their linearization differs, which can lead to different member
>> resolution. Here's how you can see this:
>>
>> scala> trait A { type T; val m: T }
>> defined trait A
>>
>> scala> trait B { type T <: String }
>> defined trait B
>>
>> scala> val x: A with B = new A with B { type T = String; val m = "abc" }
>> x: A with B = $anon$1@1a896a4
>
> Why is this type intersection even allowed? A#T and B#T are not related in
> any way, so this is akin to structural typing. It's not allowed for non-type
> members.
>
I don't understand. Both A and B postulate a type member T, with two
different constraints. The constraints are compatible and can be
combined, so there's no conflict.

Cheers

Jesper Nordenberg
Joined: 2008-12-27,
User offline. Last seen 42 years 45 weeks ago.
Re: Question on abstract types

martin odersky wrote:
> On Fri, Mar 20, 2009 at 10:33 AM, Jesper Nordenberg wrote:
>> martin odersky wrote:
>>> Currently, there is indeed a difference between A with B and B with A.
>>> Their linearization differs, which can lead to different member
>>> resolution. Here's how you can see this:
>>>
>>> scala> trait A { type T; val m: T }
>>> defined trait A
>>>
>>> scala> trait B { type T <: String }
>>> defined trait B
>>>
>>> scala> val x: A with B = new A with B { type T = String; val m = "abc" }
>>> x: A with B = $anon$1@1a896a4
>> Why is this type intersection even allowed? A#T and B#T are not related in
>> any way, so this is akin to structural typing. It's not allowed for non-type
>> members.
>>
> I don't understand. Both A and B postulate a type member T, with two
> different constraints. The constraints are compatible and can be
> combined, so there's no conflict.

Indeed, I was a bit quick on the reply button. Here's a similar example
without type members:

scala> trait A { def m : Any }
defined trait A

scala> trait B { def m : String }
defined trait B

scala> val x : A with B = new A with B { def m = "hello" }
x: A with B = $anon$1@48268a

scala> val y : B with A = x
y: B with A = $anon$1@48268a

scala> val t : String = y.m
:8: error: type mismatch;
found : Any
required: String
val t : String = y.m

As you write, this is unfortunate and clearly the order of the types in
the "with" sequence should only affect inheritance of implementation,
not the member types.

/Jesper Nordenberg

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: Re: Question on abstract types

On Fri, Mar 20, 2009 at 1:31 PM, Jesper Nordenberg wrote:
> martin odersky wrote:
>>
>> On Fri, Mar 20, 2009 at 10:33 AM, Jesper Nordenberg
>> wrote:
>>>
>>> martin odersky wrote:
>>>>
>>>> Currently, there is indeed a difference between A with B and B with A.
>>>> Their linearization differs, which can lead to different member
>>>> resolution. Here's how you can see this:
>>>>
>>>> scala> trait A { type T; val m: T }
>>>> defined trait A
>>>>
>>>> scala> trait B { type T <: String }
>>>> defined trait B
>>>>
>>>> scala> val x: A with B = new A with B { type T = String; val m = "abc" }
>>>> x: A with B = $anon$1@1a896a4
>>>
>>> Why is this type intersection even allowed? A#T and B#T are not related
>>> in
>>> any way, so this is akin to structural typing. It's not allowed for
>>> non-type
>>> members.
>>>
>> I don't understand. Both A and B postulate a type member T, with two
>> different constraints. The constraints are compatible and can be
>> combined, so there's no conflict.
>
> Indeed, I was a bit quick on the reply button. Here's a similar example
> without type members:
>
> scala> trait A { def m : Any }
> defined trait A
>
> scala> trait B { def m : String }
> defined trait B
>
> scala> val x : A with B = new A with B { def m = "hello" }
> x: A with B = $anon$1@48268a
>
> scala> val y : B with A = x
> y: B with A = $anon$1@48268a
>
> scala> val t : String = y.m
> :8: error: type mismatch;
>  found   : Any
>  required: String
>       val t : String = y.m
>
> As you write, this is unfortunate and clearly the order of the types in the
> "with" sequence should only affect inheritance of implementation, not the
> member types.
>
In retrospect, yes. When it was designed that way first, it seemed to
be parsimonious to base static as well as dynamic members on the same
linearization. But I now believe that gaining commutativity for type
conjunction is more useful.

Cheers

Christian Szegedy
Joined: 2009-02-08,
User offline. Last seen 42 years 45 weeks ago.
Re: Question on abstract types

Thanks a lot!

That makes me a bit more wary of doing something like in that in future...

On 3/20/09, martin odersky wrote:
> On Thu, Mar 19, 2009 at 8:36 PM, Christian Szegedy
> wrote:
> > Hi, I've got a rather newbie question on type bounds for abstract types.
> >
> > Consider the following example:
> >
> > trait A
> > trait B
> > trait X { type T <: A }
> > trait Y { type T <: B }
> > trait Z extends X with Y
> >
> > It fails to compile with the error message:
> >
> > error overriding type T in trait X with bounds >: Nothing <: A; type
> > T in trait Y with bounds >: Nothing <: B has incompatible type
> > Y.this.T trait Z extends X with Y
> >
> > However, if I define:
> > trait Z extends X with Y { type T <: A with B }
> > It compiles fine.
> >
> > I am just curious: Is there any good (practical or theoretical)
> > argument for the compiler not to automatically infer the type bounds
> > for T?
> >
> > One such argument would if there is some subtle difference between the
> > bound "A with B" and "B with A" which I don't know about, but would
> > make such an inference ambigous. However if I add the following
> > concrete example:
> >
> > class C extends Z { class T extends B with A }
> >
> > It still compiles fine, despite difference between "B with A" and "A with B".
> >
>
> Currently, there is indeed a difference between A with B and B with A.
> Their linearization differs, which can lead to different member
> resolution. Here's how you can see this:
>
> scala> trait A { type T; val m: T }
> defined trait A
>
> scala> trait B { type T <: String }
> defined trait B
>
> scala> val x: A with B = new A with B { type T = String; val m = "abc" }
> x: A with B = $anon$1@1a896a4
>
> scala> val s: String = x.m
> s: String = abc
>
> scala> val y: B with A = x
> y: B with A = $anon$1@1a896a4
>
> scala> val t: String = y.m
> :8: error: type mismatch;
> found : y.T
> required: String
> val t: String = y.m
> ^
>
> So, the member `m' has a different type in `x' and `y'. In `x' of type
> `A with B` it's `T <: String' whereas in `y' of type `B with A' it's
> `T <: Any'. This is a bit unfortunate. We are currently playing with
> the idea to treat type composition different from inheritance.
> Inheritance would still define a linearization, but type composition
> would be commutative.
>
> Cheers
>
>
> -- Martin
>

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland