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?
--
Seth Tisue | Northwestern University | http://tisue.net
lead developer, NetLogo: http://ccl.northwestern.edu/netlogo/
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.)
--
Johannes
-----------------------------------------------
Johannes Rudolph
http://virtual-void.net
On Thu, Jul 28, 2011 at 2:12 PM, Seth Tisue <se...@tisue.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
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).
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."
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.
Here's the actual code. The commit message attempts to communicate the issue in a less colorful fashion.
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
// 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, 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.
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.
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
Because there is no return type overriding at work, in general, only
for getClass, which, though, shows different behavior for AnyVal and
AnyRef subtypes.
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.
--
Daniel C. Sobral
I travel to the future all the time.
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])
<console>: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.
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.
On 8/3/11 9:40 AM, martin odersky wrote: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.
> 3. Sinning some more: Fiddle with lubs and glbs by never considering
> getClass as a member in a refinement generated from them.