- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Question on abstract types
Thu, 2009-03-19, 20:36
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".
Thu, 2009-03-19, 21:07
#2
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
>
Fri, 2009-03-20, 10:17
#3
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
Fri, 2009-03-20, 10:37
#4
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
Fri, 2009-03-20, 11:27
#5
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
Fri, 2009-03-20, 13:37
#6
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
Fri, 2009-03-20, 13:47
#7
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
Fri, 2009-03-20, 17:17
#8
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
>
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