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

Mixed list gets strange type

22 replies
Seth Tisue
Joined: 2008-12-16,
User offline. Last seen 34 weeks 3 days ago.

2.9.1.RC1:
scala> List(1) ++ List('a')
res0: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char <: AnyVal]}] = List(1, a)

That type's quite an eyeful. It will confuse the heck out of beginners.
I'd have preferred the least-upper-bound to come back as List[AnyVal],
like it did in 2.9.0.

This issue was reported by Simon Ochsenreither at
https://issues.scala-lang.org/browse/SI-4846 .

The change in the inferred type is the result of the fix Paul Phillips
made to https://issues.scala-lang.org/browse/SI-490, where he improved
the return type of getClass (bringing Scala up to par with Java in this
respect, to much rejoicing).

However the getClass change just more prominently exposes something
about least-upper-bound computation that hasn't changed in a long time.
The following is the same in 2.9, 2.8, and 2.7:

scala> abstract class A { def f: Class[_] }
defined class A

scala> class A1 extends A { override def f = classOf[Int] }
defined class A1

scala> class A2 extends A { override def f = classOf[Char] }
defined class A2

scala> List(new A1) ++ List(new A2)
res2: List[A{def f: java.lang.Class[_ >: Int with Char <: AnyVal]}] = List(A1@52ac5024, A2@2ec195e3)

It's surprising to me that the least-upper-bound algorithm would invent
a structural type when there weren't any in my code. But I imagine this
is very much intentional. Can anyone explain why I should consider this
a pleasant surprise, rather than an unpleasant one?

And if the least-upper-bound algorithm is doing the right thing, then I
guess we can't do anything about the List(1) ++ List('a') example?
Or can we?

Johannes Rudolph 2
Joined: 2010-02-12,
User offline. Last seen 42 years 45 weeks ago.
Re: Mixed list gets strange type

On Thu, Jul 28, 2011 at 2:12 PM, Seth Tisue wrote:
> 2.9.1.RC1:
> scala> List(1) ++ List('a')
> res0: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char <: AnyVal]}] = List(1, a)

I think the upper bound really is of little use. Though, here's a
thing you can do with the result which you couldn't do otherwise:

val a: Class[_ <: AnyVal] = res0.head.f

(Not that this would be particularly useful.)

Ismael Juma
Joined: 2009-01-07,
User offline. Last seen 42 years 45 weeks ago.
Re: Mixed list gets strange type
On Thursday, 28 July 2011 13:27:24 UTC+1, Johannes Rudolph wrote:
On Thu, Jul 28, 2011 at 2:12 PM, Seth Tisue <se [dot] [dot] [dot] [at] tisue [dot] net> wrote:
> 2.9.1.RC1:
> scala> List(1) ++ List('a')
> res0: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char <: AnyVal]}] = List(1, a)

I think the upper bound really is of little use. Though, here's a
thing you can do with the result which you couldn't do otherwise:

val a: Class[_ <: AnyVal] = res0.head.f

I guess you meant res0.head.getClass?
scala> List(1) ++ List('a')res0: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char <: AnyVal]}] = List(1, a)
scala> val a: Class[_ <: AnyVal] = res0.head.getClass<console>:8: error: type mismatch; found   : java.lang.Class[?0] where type ?0 required: Class[_ <: AnyVal]       val a: Class[_ <: AnyVal] = res0.head.getClass
Best,Ismael

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: Mixed list gets strange type


On Thu, Jul 28, 2011 at 2:12 PM, Seth Tisue <seth [at] tisue [dot] net> wrote:

2.9.1.RC1:
scala> List(1) ++ List('a')
res0: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char <: AnyVal]}] = List(1, a)

That type's quite an eyeful.  It will confuse the heck out of beginners.
I'd have preferred the least-upper-bound to come back as List[AnyVal],
like it did in 2.9.0.

This issue was reported by Simon Ochsenreither at
https://issues.scala-lang.org/browse/SI-4846 .

The change in the inferred type is the result of the fix Paul Phillips
made to https://issues.scala-lang.org/browse/SI-490, where he improved
the return type of getClass (bringing Scala up to par with Java in this
respect, to much rejoicing).

However the getClass change just more prominently exposes something
about least-upper-bound computation that hasn't changed in a long time.
The following is the same in 2.9, 2.8, and 2.7:

scala> abstract class A { def f: Class[_] }
defined class A

scala> class A1 extends A { override def f = classOf[Int] }
defined class A1

scala> class A2 extends A { override def f = classOf[Char] }
defined class A2

scala> List(new A1) ++ List(new A2)
res2: List[A{def f: java.lang.Class[_ >: Int with Char <: AnyVal]}] = List(A1@52ac5024, A2@2ec195e3)

It's surprising to me that the least-upper-bound algorithm would invent
a structural type when there weren't any in my code.  But I imagine this
is very much intentional.  Can anyone explain why I should consider this
a pleasant surprise, rather than an unpleasant one?

It should not be a surprise at all :-) Least upper bound is what it says: The least type that is a supertype of all argument types. It is completely defined from your subtyping relation. And since refinements are legal Scala types they may appear as lubs. That fact is used in a lot of Scala code.

One way out of this particular pickle would be to teach the typechecker that the glb of Char and Int is Nothing, and likewise for the other value types. I think that would fix the problem.

Cheers

 -- Martin

Johannes Rudolph 2
Joined: 2010-02-12,
User offline. Last seen 42 years 45 weeks ago.
Re: Mixed list gets strange type

On Thu, Jul 28, 2011 at 3:20 PM, ijuma wrote:
> I guess you meant res0.head.getClass?

No, I meant Seth's more general example. There it works.

It seems like the getClass definition from the structural type in

AnyVal{def getClass(): java.lang.Class[_ >: Int with Char <: AnyVal]}

is "overridden" by the one coming from AnyVal directly. In fact, this
structural type is one that can't be because the one from the
structural type is incompatible with the one from AnyVal itself
(though that may well be covered by the spec).

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: Mixed list gets strange type

On 7/28/11 6:48 AM, martin odersky wrote:
> One way out of this particular pickle would be to teach the typechecker
> that the glb of Char and Int is Nothing, and likewise for the other
> value types. I think that would fix the problem.

A little knowledge is a dangerous thing. What to do?

// Current Typy we all know and love
scala> List(1) ++ List(1L)
res0: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Long <: AnyVal]}] = List(1, 1)

// The smarter Typy who has been taught the above lesson
scala> List(1) ++ List(1L)
res0: List[AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}] = List(1, 1)

SO FAR, SO GOOD. BUT THEN...

// "I am Typy! I'll protect you, mr. x!"
scala> var x: Set[_ >: Int with Char with Long] = _
x: Set[_ >: Int with Char with Long] = null

// "Thou art above the bound."
scala> x = List(Set[Int](1), Set[Char]('c')).head
x: scala.collection.immutable.Set[_ >: Int with Char with Long] = Set(1)

// "Begone, charlatan!"
scala> x = List(Set[Int](1), Set[Byte](1)).head
:8: error: type mismatch;
found : scala.collection.immutable.Set[_6] where type _6 >: Byte with Int <: AnyVal{def getClass(): java.lang.Class[_ >: Byte with Int <: AnyVal]}
required: Set[_ >: Int with Char with Long]
Note: _6 <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
x = List(Set[Int](1), Set[Byte](1)).head
^

// After a healty swig of dr. extempore's "Glub glUb" mystery tonic...
scala> var x: Set[_ >: Int with Char with Long] = _
x: Set[_ >: Int with Char with Long] = null

// "All this jibber-jabber sounds the same to me!"
scala> x = List(Set[Int](1), Set[Char]('c')).head
:8: error: type mismatch;
found : scala.collection.immutable.Set[_4] where type _4 <: AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}
required: Set[_ >: Int with Char with Long]
Note: _4 <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
x = List(Set[Int](1), Set[Char]('c')).head
^

// "Woe betide us! Mr. x... I have failed you."

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: Mixed list gets strange type
Hi Paul,

I am not sure what change you did not smarted up the typer. My idea was to change glb so that glb(x1, x2) where x1 and x2 are different value types is Nothing. In that case I would not have expected to see things like >: Int with Char with Long in the bound.

Cheers

 -- Martin

On Tue, Aug 2, 2011 at 5:13 AM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On 7/28/11 6:48 AM, martin odersky wrote:
> One way out of this particular pickle would be to teach the typechecker
> that the glb of Char and Int is Nothing, and likewise for the other
> value types. I think that would fix the problem.

A little knowledge is a dangerous thing.  What to do?


// Current Typy we all know and love
scala> List(1) ++ List(1L)
res0: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Long <: AnyVal]}] = List(1, 1)

// The smarter Typy who has been taught the above lesson
scala> List(1) ++ List(1L)
res0: List[AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}] = List(1, 1)



SO FAR, SO GOOD.  BUT THEN...



// "I am Typy! I'll protect you, mr. x!"
scala> var x: Set[_ >: Int with Char with Long] = _
x: Set[_ >: Int with Char with Long] = null

// "Thou art above the bound."
scala> x = List(Set[Int](1), Set[Char]('c')).head
x: scala.collection.immutable.Set[_ >: Int with Char with Long] = Set(1)

// "Begone, charlatan!"
scala> x = List(Set[Int](1), Set[Byte](1)).head
<console>:8: error: type mismatch;
 found   : scala.collection.immutable.Set[_6] where type _6 >: Byte with Int <: AnyVal{def getClass(): java.lang.Class[_ >: Byte with Int <: AnyVal]}
 required: Set[_ >: Int with Char with Long]
Note: _6 <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
      x = List(Set[Int](1), Set[Byte](1)).head
                                          ^


// After a healty swig of dr. extempore's "Glub glUb" mystery tonic...
scala> var x: Set[_ >: Int with Char with Long] = _
x: Set[_ >: Int with Char with Long] = null

// "All this jibber-jabber sounds the same to me!"
scala> x = List(Set[Int](1), Set[Char]('c')).head
<console>:8: error: type mismatch;
 found   : scala.collection.immutable.Set[_4] where type _4 <: AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}
 required: Set[_ >: Int with Char with Long]
Note: _4 <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
      x = List(Set[Int](1), Set[Char]('c')).head
                                            ^

// "Woe betide us! Mr. x... I have failed you."



--
Martin Odersky
Prof., EPFL and Chairman, Typesafe
PSED, 1015 Lausanne, Switzerland
Tel. EPFL: +41 21 693 6863
Tel. Typesafe: +41 21 691 4967

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: Mixed list gets strange type

On 8/2/11 2:14 AM, martin odersky wrote:
> I am not sure what change you did not smarted up the typer. My idea was
> to change glb so that glb(x1, x2) where x1 and x2 are different value
> types is Nothing.

That's what I did.

> In that case I would not have expected to see things
> like >: Int with Char with Long in the bound.

That is in the bound because I typed it in there. What do you
anticipate happening when I enter this line?

scala> var x: Set[_ >: Int with Char with Long] = _

The change to the glb calculation has no effect on this line. But the
behavior is different afterward. Look at the original email again, with
the knowledge that those bounds are in there because I entered them, and
the change is right there.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: Mixed list gets strange type

On 8/2/11 2:14 AM, martin odersky wrote:
> I am not sure what change you did not smarted up the typer. My idea was
> to change glb so that glb(x1, x2) where x1 and x2 are different value
> types is Nothing. In that case I would not have expected to see things
> like >: Int with Char with Long in the bound.

Here's the actual code. The commit message attempts to communicate the issue in a less colorful fashion.

https://github.com/paulp/scala-dev/commit/07037bd583

soc
Joined: 2010-02-07,
User offline. Last seen 34 weeks 5 days ago.
Re: Mixed list gets strange type

// Current Typy we all know and love
scala> List(1) ++ List(1L)
res0: List[AnyVal{def getClass(): java.lang.Class[_>: Int with Long<: AnyVal]}] = List(1, 1)

// The smarter Typy who has been taught the above lesson
scala> List(1) ++ List(1L)
res0: List[AnyVal{def getClass(): java.lang.Class[_<: AnyVal]}] = List(1, 1)

soc
Joined: 2010-02-07,
User offline. Last seen 34 weeks 5 days ago.
Re: Mixed list gets strange type

Hi,

first of all sorry, for the email before that, I hit "Send" too fast...
> // Current Typy we all know and love
> scala> List(1) ++ List(1L)
> res0: List[AnyVal{def getClass(): java.lang.Class[_>: Int with Long<: AnyVal]}] = List(1, 1)
>
> // The smarter Typy who has been taught the above lesson
> scala> List(1) ++ List(1L)
> res0: List[AnyVal{def getClass(): java.lang.Class[_<: AnyVal]}] = List(1, 1)
>

In my opinion the problem is not whether it should be
>AnyVal{def getClass(): java.lang.Class[_ >: Int with Long <: AnyVal]}
or better
>AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}.

Both signatures a almost equally bad from a "naive" POV.

Isn't it possible to get rid of the whole "{def getClass ...}" alltogether?

The current situation makes me think that the appropriate type for
List[AnyRef] should be something like

List[AnyRef{def !=(arg0: AnyRef): Boolean; def ##(): Int; def ==(arg0:
AnyRef): Boolean; def eq(arg0: AnyRef): Boolean; def equals(arg0: Any):
Boolean; def getCLass(): java.lang.Class[_]; def hashCode(): Int; def
ne(arg0: AnyRef): Boolean; def notify(): Unit; def notifyAll(): Unit;
def synchronized[T0](arg0: => T0): T0; def toString(): String; def
wait(): Unit; def wait(arg0: Long, arg1: Int): Unit; def wait(arg0:
Long): Unit}]

too. But we don't do that, we take those methods for granted. Isn't it
possible to take AnyVal's getClass for granted, too, so that we end up
with the old List[AnyVal] again?

Thanks and bye,

Simon

Seth Tisue 2
Joined: 2011-07-21,
User offline. Last seen 42 years 45 weeks ago.
Re: Mixed list gets strange type
On Monday, August 1, 2011 11:13:41 PM UTC-4, Paul Phillips wrote:

// The smarter Typy who has been taught the above lesson
scala> List(1) ++ List(1L)
res0: List[AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}] = List(1, 1)

SO FAR, SO GOOD.


Well, sort of. I was hoping for plain List[AnyVal] here, not List[AnyVal{blah blah blah OMG Scala is scary}].
I noticed just now that currently on trunk we have:
scala> def f(x: AnyVal) = x.getClassf: (x: AnyVal)java.lang.Class[_]
Oops! If getClass on AnyVal was typed as Class[_ <: AnyVal] instead of Class[_], then in the above example we ought to get List[AnyVal] like we want, right?
(thanks to Simon and Daniel on #scala for discussion)
-- Seth Tisue | Northwestern University | http://tisue.netlead developer, NetLogo: http://ccl.northwestern.edu/netlogo/

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: Mixed list gets strange type

On 8/2/11 7:36 AM, Simon Ochsenreither wrote:
> In my opinion the problem is not whether it should be
>> AnyVal{def getClass(): java.lang.Class[_ >: Int with Long <:
>> AnyVal]}
> or better
>> AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}.
>
> Both signatures a almost equally bad from a "naive" POV.

Well, better start thinking of ways to hide them. As already
demonstrated, as soon as you start throwing away information you lose
functionality.

> Isn't it possible to get rid of the whole "{def getClass ...}"
> alltogether?

Yes. We could infer as general a type as you can imagine. The question
is what will stop working.

> The current situation makes me think that the appropriate type for
> List[AnyRef] should be something like ...

Why? What additional information is present?

> Isn't it possible to take AnyVal's getClass for granted, too, so
> that we end up with the old List[AnyVal] again?

Your question cannot be answered until you address the fact that things
stop working. You have to make a conscious choice about what you think
is worth breaking. Please re-read my initial email.

On 8/2/11 7:42 AM, Seth Tisue wrote:
> Oops! If getClass on AnyVal was typed as Class[_ <: AnyVal] instead
> of Class[_], then in the above example we ought to get List[AnyVal]
> like we want, right?

Yes, that looks like something I can fix. But getting List[AnyVal] like
you want presupposes some resolution to the problem at hand, because I'm
not interested in breaking lower bounds over this, and I assume after I
successfully communicate the issue to martin, he will be no more
enthusiastic.

Johannes Rudolph 2
Joined: 2010-02-12,
User offline. Last seen 42 years 45 weeks ago.
Re: Mixed list gets strange type

Why can't `AnyVal.getClass` be defined as:

def getClass(): Class[_ <: AnyVal]

Shouldn't that solve the problem?

On Tue, Aug 2, 2011 at 5:08 PM, Paul Phillips wrote:
> On 8/2/11 7:36 AM, Simon Ochsenreither wrote:
>>
>> In my opinion the problem is not whether it should be
>>>
>>> AnyVal{def getClass(): java.lang.Class[_ >: Int with Long <:
>>> AnyVal]}
>>
>> or better
>>>
>>> AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}.
>>
>> Both signatures a almost equally bad from a "naive" POV.
>
> Well, better start thinking of ways to hide them.  As already
> demonstrated, as soon as you start throwing away information you lose
> functionality.
>
>> Isn't it possible to get rid of the whole "{def getClass ...}"
>> alltogether?
>
> Yes.  We could infer as general a type as you can imagine.  The question
> is what will stop working.
>
>> The current situation makes me think that the appropriate type for
>> List[AnyRef] should be something like ...
>
> Why? What additional information is present?
>
>> Isn't it possible to take AnyVal's getClass for granted, too, so
>> that we end up with the old List[AnyVal] again?
>
> Your question cannot be answered until you address the fact that things
> stop working.  You have to make a conscious choice about what you think
> is worth breaking.  Please re-read my initial email.
>
> On 8/2/11 7:42 AM, Seth Tisue wrote:
>>
>> Oops! If getClass on AnyVal was typed as Class[_ <: AnyVal] instead
>> of Class[_], then in the above example we ought to get List[AnyVal]
>> like we want, right?
>
> Yes, that looks like something I can fix.  But getting List[AnyVal] like you
> want presupposes some resolution to the problem at hand, because I'm not
> interested in breaking lower bounds over this, and I assume after I
> successfully communicate the issue to martin, he will be no more
> enthusiastic.
>

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: Mixed list gets strange type

On 8/2/11 8:53 AM, Johannes Rudolph wrote:
> Why can't `AnyVal.getClass` be defined as:
>
> def getClass(): Class[_<: AnyVal]
>
> Shouldn't that solve the problem?

That is what seth suggested, which I said we can do, and which will not solve the problem, because it will still infer the more precise bounds which it presently infers.

Let's take getClass out of the equation here:

scala> trait Foo { type U ; def f: U }
defined trait Foo

scala> object O1 extends Foo { type U = Long ; def f = 0L }
defined module O1

scala> object O2 extends Foo { type U = Short ; def f = 0.toShort }
defined module O2

scala> def g = List(O1, O2).head
g: ScalaObject with Foo{def f: AnyVal; type U >: Short with Long <: AnyVal}

The return type restricts the possible results to Short and Long. If the glb of Short and Long is "Nothing", then the return type of f allows all nine AnyVals. This is not something it makes sense to do for cosmetic reasons. I'm not saying we shouldn't hide it, but I am saying we shouldn't hide it by crippling the inferencer.

DaveScala
Joined: 2011-03-18,
User offline. Last seen 1 year 21 weeks ago.
Re: Mixed list gets strange type

The algorithm is not consistent from three lists and up:
scala> (List(1) ++ List('a')) ++ List(1L)
res2: List[AnyVal] = List(1, a, 1)

scala> List(1) ++ (List('a') ++ List(1L))
res3: List[AnyVal] = List(1, a, 1)

scala> List(1) ++ List('a') ++ List(1L)
res4: List[AnyVal] = List(1, a, 1)

Thus:

scala> List(1) ++ List('a')
res5: List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char
<: AnyVal]}
] = List(1, a)

But appending a 3rd list:

scala> res5 ++ List(1L)
res6: List[AnyVal] = List(1, a, 1)

So why is appending List(1L) suddenly becoming List[AnyVal] and not
List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char with
Long <: AnyVal]}]

Apart from this: I think the upperboundary <: AnyVal is superfluous,
it can be removed so that it becomes:
List[AnyVal{def getClass(): java.lang.Class[_ >: Int with Char with
Long]}]

I think it is a design decision whether there should be ad hoc types
or only types declared from inheritance tree,
so structural vs nominative types.

In my opinion List[AnyVal] is better so I go for nominative typing.
The adhoc type are indeed accurate but I don't see any benefit.
But it is indeed information loss if it is replaced by a pre-declared
type.

I read
AnyVal{def getClass(): java.lang.Class[_ >: Int with Char with Long]}

as

AnyVal but restricted to Int, Char, Long and their subtypes (if they
could be subtyped)

so I don't find it that hard to read it but it can be scary for
beginners

Maybe these adhoc structural types could be supported with an
annotation if advanced programmers want to use them.

soc
Joined: 2010-02-07,
User offline. Last seen 34 weeks 5 days ago.
Re: Mixed list gets strange type

Hi,
>
> Well, sort of. I was hoping for plain List[AnyVal] here, not
> List[AnyVal{blah blah blah OMG Scala is scary}].
That's _exactly_ what I meant.

Why does everything work without littering AnyRef with AnyRef{def
!=(arg0: AnyRef): Boolean; def ##(): Int; def ==(arg0: AnyRef): Boolean;
def eq(arg0: AnyRef): Boolean; def equals(arg0: Any): Boolean; def
getCLass(): java.lang.Class[_]; def hashCode(): Int; def ne(arg0:
AnyRef): Boolean; def notify(): Unit; def notifyAll(): Unit; def
synchronized[T0](arg0: => T0): T0; def toString(): String; def wait():
Unit; def wait(arg0: Long, arg1: Int): Unit; def wait(arg0: Long): Unit}
but it doesn't work for AnyVal?

Thanks and bye

dcsobral
Joined: 2009-04-23,
User offline. Last seen 38 weeks 5 days ago.
Re: Mixed list gets strange type

On Tue, Aug 2, 2011 at 11:42, Seth Tisue wrote:
> On Monday, August 1, 2011 11:13:41 PM UTC-4, Paul Phillips wrote:
>>
>> // The smarter Typy who has been taught the above lesson
>> scala> List(1) ++ List(1L)
>> res0: List[AnyVal{def getClass(): java.lang.Class[_ <: AnyVal]}] = List(1,
>> 1)
>>
>> SO FAR, SO GOOD.
>
> Well, sort of. I was hoping for plain List[AnyVal] here, not
> List[AnyVal{blah blah blah OMG Scala is scary}].
> I noticed just now that currently on trunk we have:
> scala> def f(x: AnyVal) = x.getClass
> f: (x: AnyVal)java.lang.Class[_]
> Oops! If getClass on AnyVal was typed as Class[_ <: AnyVal] instead of
> Class[_], then in the above example we ought to get List[AnyVal] like we
> want, right?
> (thanks to Simon and Daniel on #scala for discussion)

Not List[AnyVal], List[_ <: AnyVal] indeed -- there's a recent
discussion about that at the very beginning of Kotlin's thread that
paulp started.

But look here:

scala> def f(x: AnyRef) = x.getClass
f: (x: AnyRef)java.lang.Class[_ <: AnyRef]

scala> def f(x: AnyVal) = x.getClass
f: (x: AnyVal)java.lang.Class[_]

This difference doesn't look right.

Johannes Rudolph 2
Joined: 2010-02-12,
User offline. Last seen 42 years 45 weeks ago.
Re: Mixed list gets strange type

On Tue, Aug 2, 2011 at 6:21 PM, Simon Ochsenreither
wrote:
> Why does everything work without littering AnyRef with AnyRef{def !=(arg0:
> AnyRef): Boolean; def ##(): Int; def ==(arg0: AnyRef): Boolean; def eq(arg0:
> AnyRef): Boolean; def equals(arg0: Any): Boolean; def getCLass():
> java.lang.Class[_]; def hashCode(): Int; def ne(arg0: AnyRef): Boolean; def
> notify(): Unit; def notifyAll(): Unit; def synchronized[T0](arg0: => T0):
> T0; def toString(): String; def wait(): Unit; def wait(arg0: Long, arg1:
> Int): Unit; def wait(arg0: Long): Unit}
> but it doesn't work for AnyVal?

Because there is no return type overriding at work, in general, only
for getClass, which, though, shows different behavior for AnyVal and
AnyRef subtypes.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: Mixed list gets strange type

On 8/2/11 9:35 AM, Daniel Sobral wrote:
> But look here:
>
> scala> def f(x: AnyRef) = x.getClass
> f: (x: AnyRef)java.lang.Class[_<: AnyRef]
>
> scala> def f(x: AnyVal) = x.getClass
> f: (x: AnyVal)java.lang.Class[_]

Now that everyone and their brother has made this observation, I have rediscovered why I didn't do that in the first place. Fortunately I tend to leave a trail of breadcomments to help future me out.

// Note: AnyVal cannot be Class[_ <: AnyVal] because if the static type of the
// receiver is AnyVal, it implies the receiver is boxed, so the correct
// class object is that of java.lang.Integer, not Int.

Here is what it is talking about:

scala> def f(x: AnyVal) = x.getClass
f: (x: AnyVal)java.lang.Class[_]

scala> f(5L)
res0: java.lang.Class[_] = class java.lang.Long

scala> (res0: Class[_ <: AnyVal])
:10: error: type mismatch;
found : java.lang.Class[?0] where type ?0
required: Class[_ <: AnyVal]
(res0: Class[_ <: AnyVal])
^

See, Class[_ <: AnyVal] would be a lie.

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: Mixed list gets strange type
OK, looking at the commit and the code I see now that fiddling with lubs and glbs is fraught with danger. lubs and glbs have a very fixed meaning, if you define subtyping you have defined lubs and glbs with it. We break that correspondence at our peril.

It seems the only thing we could still consider involves specifically getClass. In a sense, by treating the type of getClass specially, we have sinned against the purity of the type system (we sinned for a good purpose, but we sinned nevertheless). To get out of this, we seem to have the following options:

1. The path back to virtue: Eliminate all special treament of getClass. I predict there will be much weeping if we do.

2. The fig leaf: Keep the special treatment of getClass but eliminate all overridden versions in value classes. This might complicate bytecode generation, but seems otherwise feasible.

3. Sinning some more: Fiddle with lubs and glbs by never considering getClass as a member in a refinement generated from them. In a sense
we have justification for this by saying we already treated getClass in an ad-hoc way, so we might as well go all the way.

Cheers

 -- Martin


extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: Mixed list gets strange type

On 8/3/11 9:40 AM, martin odersky wrote:
> 3. Sinning some more: Fiddle with lubs and glbs by never considering
> getClass as a member in a refinement generated from them.

We can even do this without sinning (at least so our sin lawyer might argue) by excluding all members of root classes from being inferred in refinements. It just so happens that the only one which stands any chance of doing so is getClass, because all the other members of java.lang.Object are unrefinable, either due to being final, returning void, or returning a final class.

I wrote that, and then I went looking: oh yeah, there's one more. Here's an interesting bug. Those clone methods aren't protected[lang] anymore!

scala> class A { override def clone(): A = new A }
defined class A

scala> class C { override def clone(): A = new A }
defined class C

scala> List(new A, new C)
res0: List[ScalaObject{protected[package lang] def clone(): A}] = List(A@2b619bca, C@153b0106)

Maybe it's a blessing in disguise and we should dump clone() from consideration too.

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: Mixed list gets strange type


On Wed, Aug 3, 2011 at 8:43 PM, Paul Phillips <paulp [at] improving [dot] org> wrote:
On 8/3/11 9:40 AM, martin odersky wrote:
> 3. Sinning some more: Fiddle with lubs and glbs by never considering
> getClass as a member in a refinement generated from them.

We can even do this without sinning (at least so our sin lawyer might argue) by excluding all members of root classes from being inferred in refinements.

You must have a very clever sin lawyer!

But just treating getClass has the advantage that we do not run into problems if at one point or on some platform there is a genuinely overridable root class.

Looking from it from the spec point of view: It's awkward to say members so and so are excluded from inferred refinements. That's an operational view. Inference is constraint-based and lubs and glbs are only a means to get best solutions of constraints. If we start to fiddle with them in the spec the whole thing will have to be put on a (complicated, messy) operational basis.

Treating getClass specially can be pushed from the member to the call in the spec. I.e, as a member getClass always has type Class[_]. However,
in a call to

  x.getClass,

the result type is narrowed to Class[_ <: T] where T is the type of x.

That's a much nicer spec than the alternatives I can see. We can get
there implementation-wise either by removing overridden getClass defs in value classes, or by treating getClass specially in lubs or glbs.

Cheers

 -- Martin

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