Why #specialized annotation doesn't work for asInstanceOf? - scala

As far as i understand, #specialized annotation should generate some unboxed code for every primitive type i mentioned, but this doesn't work:
scala> def aaa[#specialized(Int, Double, Float, Long) T] = (5.0).doubleValue.asInstanceOf[T]
aaa: [T]=> T
scala> aaa[Int]
unrecoverable error (inside interpreter/compiler)
This compiles:
scala> def aaa[#specialized(Int, Double, Float, Long) T](a: T) = (5.0).doubleValue.asInstanceOf[T]
aaa: [T]=> T
scala> aaa[Int](0)
ClassCastException
But it still uses boxed type for asInstanceOf[T]. This obviously works:
scala> (5.0).asInstanceOf[Int]
res28: Int = 5
UPDATE:
Type erasure and answers like that Writing a generic cast function Scala has nothing to do with my problem. Type erasure just preventing compiler from adding typecast byte-code operation for generics, but eventually it will be added - see ClassCastException (generated by this op) in my REPL

The problem was in Scala REPL - #specialized doesn't work there. Compiling def aaa[#specialized(Int) T] = (5.0).asInstanceOf[T] with scalac gives:
public <T> T aaa();
Code:
0: ldc2_w #15 // double 5.0d
3: invokestatic #22 // Method scala/runtime/BoxesRunTime
.boxToDouble:(D)Ljava/lang/Double;
6: areturn
public int aaa$mIc$sp();
Code:
0: ldc2_w #15 // double 5.0d
3: d2i
4: ireturn
d2i is exactly what i was expecting. And of course everything works fine with scalac (so I don't need pattern matching for every possible type). So it's just the issue with interpreter.

The method is specialized, but under separate compilation (i.e., different lines), the specialized method isn't invoked.
In the following, b.B.f works, c.B.f is broken.
$ scala -Xprint:typer,cleanup
scala> :pa -raw
// Entering paste mode (ctrl-D to finish)
package a { object A { def aaa[#specialized(Int) T] = (5.0).doubleValue.asInstanceOf[T] }}
package b { object B { def f = a.A.aaa[Int] }}
// Exiting paste mode, now interpreting.
[[syntax trees at end of typer]] // <pastie>
package <empty> {
package a {
object A extends scala.AnyRef {
def <init>(): a.A.type = {
A.super.<init>();
()
};
def aaa[#specialized(scala.Int) T]: T = scala.this.Predef.double2Double(5.0).doubleValue().asInstanceOf[T]
}
};
package b {
object B extends scala.AnyRef {
def <init>(): b.B.type = {
B.super.<init>();
()
};
def f: Int = a.A.aaa[Int]
}
}
}
[[syntax trees at end of cleanup]] // <pastie>
package <empty> {
package a {
object A extends Object {
def aaa(): Object = scala.Double.box(scala.this.Predef.double2Double(5.0).doubleValue());
<specialized> def aaa$mIc$sp(): Int = scala.this.Predef.double2Double(5.0).doubleValue().toInt();
def <init>(): a.A.type = {
A.super.<init>();
()
}
}
};
package b {
object B extends Object {
def f(): Int = a.A.aaa$mIc$sp();
def <init>(): b.B.type = {
B.super.<init>();
()
}
}
}
}
scala> :pa -raw
// Entering paste mode (ctrl-D to finish)
package c { object B { def f = a.A.aaa[Int] }}
// Exiting paste mode, now interpreting.
[[syntax trees at end of typer]] // <pastie>
package c {
object B extends scala.AnyRef {
def <init>(): c.B.type = {
B.super.<init>();
()
};
def f: Int = a.A.aaa[Int]
}
}
[[syntax trees at end of cleanup]] // <pastie>
package c {
object B extends Object {
def f(): Int = scala.Int.unbox(a.A.aaa());
def <init>(): c.B.type = {
B.super.<init>();
()
}
}
}

Perhaps you just need a more recent compiler? here's what I get with 2.11.2:
Welcome to Scala version 2.11.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_72).
Type in expressions to have them evaluated.
Type :help for more information.
scala> def aaa[#specialized(Int, Double, Float, Long) T] = (5.0).doubleValue.asInstanceOf[T]
aaa: [T]=> T
scala> aaa[Int]
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:105)
... 33 elided
scala> aaa[Float]
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Float
at scala.runtime.BoxesRunTime.unboxToFloat(BoxesRunTime.java:113)
... 33 elided
scala> aaa[Double]
res2: Double = 5.0
scala> 5.0.asInstanceOf[Int]
res3: Int = 5
scala> 5.0.asInstanceOf[Integer]
java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
... 33 elided
Note that you can directly do 5.0.asInstanceOf[Int] but not 5.0.asInstanceOf[Integer]. Also note that the ClassCastException that you get with aaa[Int] is referring to java.lang.Integer not Scala's Int class. I suspect what goes on here is that the #specialized annotation is generating a function, aaa, that has an "Int" specialization but because of type erasure and boxing is converting that to java.lang.Integer and there is no automatic conversion with asInstanceOf between Double and Integer.

Related

Scala apply method call as parentheses conflicts with implicit parameters

There is a note in Cay Horstmann's book "Scala for the Impatient" about the apply method:
Occasionally, the () notation conflicts with another Scala feature:
implicit parameters. For example, the expression "Bonjour".sorted(3)
yields an error because the sorted method can optionally be called
with an ordering, but 3 is not a valid ordering.
The solution is to assign "Bonjour".sorted to a variable and call apply on it, for example:
val result = "Bonjour".sorted
result(3)
Or call apply explicitly:
"Bonjour".sorted.apply(3)
But why this doesn't work and produces a compile error:
("Bonjour".sorted)(3)
The sorted method returns a String, which can be imlicitly converted to a StringOps and parentheses are used to wrap the string expression.
Why compiler doesn't accept to call the apply method of a StringOps?
You can use -Xprint:parser to see that the parens are discarded early:
scala> implicit class x(val s: String) { def scaled(implicit i: Int) = s * i }
defined class x
scala> "hi".scaled(5)
res0: String = hihihihihi
scala> { implicit val n: Int = 5 ; "hi".scaled }
res1: String = hihihihihi
scala> "hi".scaled(5)(3)
res2: Char = i
scala> { implicit val n: Int = 5 ; ("hi".scaled)(3) }
res3: String = hihihi
scala> :se -Xprint:parser
scala> { implicit val n: Int = 5 ; ("hi".scaled)(3) }
[[syntax trees at end of parser]] // <console>
package $line8 {
object $read extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
import $line3.$read.$iw.$iw.x;
object $iw extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
val res4 = {
implicit val n: Int = 5;
"hi".scaled(3)
}
}
}
}
}
res4: String = hihihi
scala>
The extra parens do nothing. The compiler just sees an application expr(args). Because it's an application, you don't get "implicit application" conversion.
In any case, the meaning of scaled, a method, depends on the expected type.
The reason we expect the extra parens to make a difference is that parens override precedence of operators. But (x) is just x.
Possibly the spec is actually clear about this:
e(args) requires that e be applicable to the args. In particular, the args are typechecked according to the parameter types of e.
e(args) is taken as e.apply(args) if e is a value, but scaled is a method.
You're hoping for "implicit application" to insert the implicit args, but that only applies when e is not already applied. Or that (e)(args) could be taken as (e(_))(args), that is, (x => e(x))(arg).
When written as e.apply(arg), the e is not an application like e(arg), so you benefit from conversions like implicit application.

scala: generic function from type T to Type T while performing transformations of the input

I have the following very contrived example:
def f[T](t: T): T = {
t match {
case a : Int => a * 2
case _ => t
}
}
// Exiting paste mode, now interpreting.
<console>:14: error: type mismatch;
found : Int
required: T
case a : Int => a * 2
^
My questions are:
Why does the compiler not reconcile the Int as T?
except for converting to (a * 2).asInstanceOf[T] is there any other strategy to manipulate something polymorphically returning an instance of the same type when nothing special is known about T? (save for type classes?)
Notice please the following type class implementation, is even less elegant:
trait Manipulator[A] {
def manip(a: A): A
}
implicit object IntManip extends Manipulator[Int] {
override def manip(a: Int): Int = a * 2
}
implicit object WhateverManip extends Manipulator[Any] {
override def manip(a: Any): Any = a
}
def g[T](t: T)(implicit manipulator: Manipulator[T]) = {
manipulator.manip(t)
}
g(2)
g(2.3.asInstanceOf[Any])(WhateverManip)
I would have been able to do away with the asInstanceOf[Any] had Manipulator been contravariant, but...:
trait Manipulator[-A] {
def manip(a: A): A
}
// Exiting paste mode, now interpreting.
<console>:13: error: contravariant type A occurs in covariant position in type (a: A)A of method manip
def manip(a: A): A
With literal types enabled you can call f[3](3). Without literal types, in standard, current, no-options Scala you can still do val a = new Integer(3); f[a.type](a). If you fix type parameter T to be 3, this means the return type also has to be 3. And the type 3 is only inhabited by the value 3, which means it would be wrong to return 3 * 2. But 3 is also an instance of Int, so case a: Int will work for it. Only that doesn't prove that T =:= Int.
Without type classes you can do close to nothing with t if you don't use type casing and type casting. That is actually the beauty of parametricity.
You can fix your typeclass by introducing priority if you want to have a default fall-back instance.
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Manipulator[A] {
def manip(a: A): A
}
object Manipulator extends LowerPriority {
implicit object IntManip extends Manipulator[Int] {
override def manip(a: Int): Int = a * 2
}
}
trait LowerPriority {
implicit def WhateverManip[T] = new Manipulator[T] {
override def manip(a: T): T = a
}
}
def g[T](t: T)(implicit manipulator: Manipulator[T]) = {
manipulator.manip(t)
}
// Exiting paste mode, now interpreting.
scala> g(2)
res0: Int = 4
scala> g(2.3)
res1: Double = 2.3

Scala: How can an import prevent finding an implicit value?

I could use suggestions debugging an implicit:
I want to use the implicit, x:
type T
trait HasT {
implicit def x: T = ...
}
But I also need a wildcard import from some package foo. I've tried two different ways of introducing both:
class UseT extends HasT {
import foo._
implicitly[T] // fails! "could not find implicit value"
// use foo stuff
}
and
class UseT {
object hasT extends HasT
import hasT.x
import foo._
implicitly[T] // fails! "could not find implicit value"
}
Both fail with "could not find" (not "ambiguous implicits values").
This happens while implicit identifier x: T is accessible at the point of method call via either inheritance or importing.
My workaround is to rebind x to an implicit val before the import. Both of the following work:
implicit val x2: T = implicitly[T]
import foo._
implicitly[T] // works!
and
implicit val x2: T = x
import foo._
implicitly[T] // works!
What value could possibly be in foo to cause this behavior?
My first guess is that there is some competing implicit in foo, but if it were higher priority, the following implicitly would still work, and if it were an ambiguous implicit, I'd be getting a different a different error.
edit: Miles Sabin's guess was correct! I found the shadowing implicit: timeColumnType. I still don't completely understand how this works given Som Snytt's observation that the shadowing implicit was wildcard imported (lower priority) while the shadowed was inherited (part of highest priority), so I'll leave the whole post here for posterity.
retracted: A second guess, offered by miles sabin, is implicit shadowing. I've since clarified my post to exclude that possibility. That case would be consistent with my errors if I had tried package hasT extends HasT; import hasT._, but As som-snytt points out, those two cases would not result in shadowing.
In my specific case, this can be confirmed by changing the name of the implicit I'm trying to use.
(This is false. I likely missed a publishLocal or reload while using this test to verify.)
context: I'm actually trying to use slick. The implicit T above is actually a column type mapping:
import slick.driver.JdbcProfile
class Custom { ... } // stored as `Long` in postgres
trait ColumnTypes {
val profile: JdbcProfile
import profile.api._ // this is `foo` above
type T = profile.BaseColumnType[Custom]
implicit def customColumnType: T =
MappedColumnType.base[Custom, Long](_.toLong, Custom.fromLong)
}
class DatabaseSchema(val profile: JdbcProfile) extends ColumnTypes {
// `implicitly[T]` does not fail here.
import profile.api._ // this is also `foo` above
// `implicitly[T]` fails here, but it's needed for the following:
class CustomTable(tag: Tag) extends Table[Custom](tag, "CUSTOMS") {
// following fails unless I rebind customColumnType to a local implicit
def custom = column[Custom]("CUSTOM")
def * = custom
}
}
The type of api/foo is JdbcProfile.API. The offending implicit is probably here, but I can't tell why. I'll try blocking some of those from being imported and see if I can narrow it down.
I think it's most likely that foo contains a definition named x. When (wildcard) imported from foo it shadows the local definition,
scala> object foo { val x: Boolean = true }
defined object foo
scala> implicit val x: Int = 23
x: Int = 23
scala> implicitly[Int]
res0: Int = 23
scala> import foo._
import foo._
scala> implicitly[Int]
<console>:17: error: could not find implicit value for parameter e: Int
implicitly[Int]
^
This is clearly a bug in implicit search.
First, eligible are all identifiers x that can be accessed at the
point of the method call without a prefix and that denote an implicit
definition or an implicit parameter. An eligible identifier may thus
be a local name, or a member of an enclosing template, or it may be
have been made accessible without a prefix through an import clause.
In the example, unprefixed x refers to the inherited symbol. X.x is not accessible without a prefix.
Implicit search is fumbling the import.
$ scala
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.
scala> :pa
// Entering paste mode (ctrl-D to finish)
object X { def x: Int = 42 }
trait T { def x: Int = 17 }
object Y extends T {
import X._
def f = x
}
// Exiting paste mode, now interpreting.
defined object X
defined trait T
defined object Y
scala> Y.f
res0: Int = 17
scala> :pa
// Entering paste mode (ctrl-D to finish)
object X { implicit def x: Int = 42 }
trait T { implicit def x: Int = 17 }
object Y extends T {
import X._
def f: Int = implicitly[Int]
}
// Exiting paste mode, now interpreting.
<pastie>:19: error: could not find implicit value for parameter e: Int
def f: Int = implicitly[Int]
^
scala> :pa
// Entering paste mode (ctrl-D to finish)
object X { implicit def x: Int = 42 }
trait T { implicit def x: Int = 17 }
object Y extends T {
import X.{x => _, _} // avoids bug, but is redundant
def f: Int = implicitly[Int]
}
// Exiting paste mode, now interpreting.
defined object X
defined trait T
defined object Y
scala>
The other example using the REPL is scoped this way:
scala> :pa
// Entering paste mode (ctrl-D to finish)
object X { def x: Int = 42 }
object Y { implicit def x: Int = 17 }
object Z {
import Y.x
def f = {
import X._
x
}
}
// Exiting paste mode, now interpreting.
<pastie>:19: error: reference to x is ambiguous;
it is imported twice in the same scope by
import X._
and import Y.x
x
^
Where x is not available at all, and the implicit is correctly excluded.
Just to confirm:
$ scala -Xlog-implicits
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.
scala> :pa
// Entering paste mode (ctrl-D to finish)
object X { implicit def x: Int = 42 }
trait T { implicit def x: Int = 17 }
object Y extends T {
import X._
def f: Int = implicitly[Int]
}
// Exiting paste mode, now interpreting.
<console>:17: x is not a valid implicit value for Int because:
candidate implicit method x in object X is shadowed by method x in trait T
def f: Int = implicitly[Int]
^
<console>:17: x is not a valid implicit value for Int because:
candidate implicit method x in object X is shadowed by method x in trait T
def f: Int = implicitly[Int]
^
<console>:17: error: could not find implicit value for parameter e: Int
def f: Int = implicitly[Int]
^
scala>
Probably https://issues.scala-lang.org/browse/SI-9208

scala - trying to print overridden toString method

the bellow code :
scala> class A {
| def hi = "Hello from A"
| override def toString = getClass.getName
| }
defined class A
scala> val a = new A()
a: A = A
scala> a.toString
res10: String = A
scala> println(s"${a.toString}")
$line31.$read$$iw$$iw$A
It is printing ok when using a.toString expression, not when using println(s"${a.toString}"). The problem is getClass.getName. In other cases it works nice.
Thanks in advance for any help
REPL filters its output to hide template wrappings.
$ scala
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_92).
Type in expressions for evaluation. Or try :help.
scala> class A
defined class A
scala> val a = new A
a: A = A#4926097b
scala> a.getClass
res0: Class[_ <: A] = class A
scala> $intp.isettings.
allSettings deprecation deprecation_= maxAutoprintCompletion maxPrintString toString unwrapStrings
scala> $intp.isettings.unwrapStrings = false
$intp.isettings.unwrapStrings: Boolean = false
scala> a.getClass
res1: Class[_ <: A] = class $line3.$read$$iw$$iw$A
You can also compare output clipping:
scala> (1 to 1000).mkString
res2: String = 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629...
scala> println((1 to 1000).mkString)
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
Scroll right to see the ellipsis on the first line.
Since names of classes inside REPL are different, REPL needs to convert internal names back. It does it well enough when displaying strings, but fails when string is passed to an external method, e.g. println or toList:
scala> a.toString
res1: String = A
scala> a.toString.toList
res2: List[Char] = List($, l, i, n, e, 4, ., $, r, e, a, d, $, $, i, w, $, $, i, w, $, A)
scala> "$line4.$read$$iw$$iw$A"
res3: String = A
Run the scala repl using: scala -Xprint:parser
Then run the successive commands. The output $line3.$read$$iw$$iw$A represents the path to the A object. $line is a package, $read and $iw are objects under which the object A is nested.
For the case of println(s"${a.toString}")
scala> println(s"${a.toString}")
[[syntax trees at end of parser]] // <console>
package $line5 {
object $read extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
import $line3.$read.$iw.$iw.A;
import $line4.$read.$iw.$iw.a;
object $iw extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
val res0 = println(StringContext("", "").s(a.toString))
}
}
}
}
[[syntax trees at end of parser]] // <console>
package $line5 {
object $eval extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
lazy val $result = $line5.$read.$iw.$iw.res0;
lazy val $print: String = {
$line5.$read.$iw.$iw;
"" <-- // SOMETHING OFF HERE! NO OUTPUT STRING BEING GENERATED?
}
}
}
$line3.$read$$iw$$iw$A
Now for the case of a.toString:
scala> a.toString
[[syntax trees at end of parser]] // <console>
package $line6 {
object $read extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
object $iw extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
import $line3.$read.$iw.$iw.A;
import $line4.$read.$iw.$iw.a;
object $iw extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
val res1 = a.toString
}
}
}
}
[[syntax trees at end of parser]] // <console>
package $line6 {
object $eval extends scala.AnyRef {
def <init>() = {
super.<init>();
()
};
lazy val $result = $line6.$read.$iw.$iw.res1;
lazy val $print: String = {
$line6.$read.$iw.$iw; // *CORRECTLY GENERATES THE RESULT STRING.*
"".$plus("res1: String = ").$plus(scala.runtime.ScalaRunTime.replStringOf($line6.$read.$iw.$iw.res1, 1000))
}
}
}
res1: String = A
It is the way REPL is compiling A. In a simple app like below there are no issues
Each line in the REPL is wrapped into it's own package.. and that auto generated package name is what you see prepended to class name A.
object ScalaApp extends App {
class A {
def hi = "Hello from A"
override def toString = getClass.getName
}
val a = new A()
println(a.toString)
println(s"${a.toString}")
}

Scala - Why are overloaded methods not called based on runtime class?

Problem
Given a simple class hierarchy
abstract class Base {}
class A extends Base {}
class B extends Base {}
And a typeclass
trait Show[T] {
def show(obj: T): String
}
With overloaded implementations
class ShowBase extends Show[Base] {
override def show(obj: Base): String = "Base"
}
object ShowA extends ShowBase {
def show(obj: A): String = "A"
}
object ShowB extends ShowBase {
def show(obj: B): String = "B"
}
When executing following test-case
Seq((new A(), ShowA), (new B(), ShowB)).foreach {
case (obj, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
}
Should produce (A,A) \n (B,B), but produces (Base,A) \n (Base,B) instead.
Question
What's going on here? Shouldn't the method with the most specific runtime type be called - Polymorphism 101?
This issue looks similar to another question where a type parameter prevents the correct resolution of which method to call. However, in my case the type parameterized show method is provided with actual implementations in contrast to type parameterized method in the other question.
Naive solution
Extending the ShowA implementation (analogue for ShowB):
object ShowA extends ShowBase {
def show(obj: A): String = "A"
override def show(obj: Base): String = {
require(obj.isInstanceOf[A], "Argument must be instance of A!")
show(obj.asInstanceOf[A])
}
}
gives the expected output. The problem is mixing A with ShowB will result in an exception.
Static overload resolution is easy to reason about: for the methods that are applicable, a method is selected as "more specific" just based on the signatures.
However,
scala> Seq((new A(), ShowA), (new B(), ShowB))
res0: Seq[(Base, ShowBase)] = List((A#2b45f918,ShowA$#7ee4acd9), (B#57101ba4,ShowB$#6286d8a3))
in ShowBase there is no overload.
scala> res0 foreach {
| case (obj: A, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
| case (obj: B, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
| }
java.lang.InternalError: Malformed class name
at java.lang.Class.getSimpleName(Class.java:1180)
at $anonfun$1.apply(<console>:17)
at $anonfun$1.apply(<console>:16)
at scala.collection.immutable.List.foreach(List.scala:383)
... 38 elided
Oh yeah, don't use getSimpleName from Scala.
scala> res0 foreach {
| case (obj: A, showImpl) => println(showImpl.show(obj), obj.getClass)
| case (obj: B, showImpl) => println(showImpl.show(obj), obj.getClass) }
(Base,class $line4.$read$$iw$$iw$A)
(Base,class $line5.$read$$iw$$iw$B)
OTOH,
scala> class ShowBase extends Show[Base] {
| override def show(obj: Base): String = "Base"
| def show(a: A) = "A" ; def show(b: B) = "B" }
defined class ShowBase
scala> Seq((new A(), new ShowBase), (new B(), new ShowBase))
res3: Seq[(Base, ShowBase)] = List((A#15c3e01a,ShowBase#6eadd61f), (B#56c4c5fd,ShowBase#10a2918c))
scala> res3 foreach {
| case (obj: A, showImpl) => println(showImpl.show(obj), obj.getClass)
| case (obj: B, showImpl) => println(showImpl.show(obj), obj.getClass) }
(A,class $line4.$read$$iw$$iw$A)
(B,class $line5.$read$$iw$$iw$B)
It's easy to imagine a macro that generates the partial function for a given interface with an overloaded method show.
Another idea, not necessarily a great one, is to let the compiler do the selection at runtime.
This is currently awkward to demonstrate in REPL. You have to import any symbols you want to use from the objects that litter your REPL history. See the issue.
scala> def imps = $intp.definedSymbolList map (s => $intp.global.exitingTyper { s.fullName }) mkString ("import ", "\nimport ", "\n")
imps: String
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
res15: Any = A
Hey, it worked!
Or, go into power mode, which sets the current phase at typer and gives you intp without the funky dollar sign. Because do we really need more dollars?
scala> :power
** Power User mode enabled - BEEP WHIR GYVE **
** :phase has been set to 'typer'. **
** scala.tools.nsc._ has been imported **
** global._, definitions._ also imported **
** Try :help, :vals, power.<tab> **
scala> def imps = intp.definedSymbolList map (_.fullName) mkString ("import ", "\nimport ", "\n")
imps: String
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
res17: Any = A
If you want to see your unsanitized imports:
scala> intp.isettings.unwrapStrings = false
intp.isettings.unwrapStrings: Boolean = false
scala> imps
res11: String =
"import $line2.$read.$iw.$iw.$intp
import $line3.$read.$iw.$iw.Base
import $line3.$read.$iw.$iw.A
import $line3.$read.$iw.$iw.B
import $line4.$read.$iw.$iw.Show
import $line5.$read.$iw.$iw.ShowA
[snip]
Once more:
scala> abstract class Base ; class A extends Base ; class B extends Base
defined class Base
defined class A
defined class B
scala> trait Show[T <: Base] { def show(obj: T): String }
defined trait Show
scala> class ShowBase extends Show[Base] { override def show(obj: Base): String = "Base" }
defined class ShowBase
scala> object ShowA extends ShowBase { def show(obj: A): String = "A" }
defined object ShowA
scala> :power
** Power User mode enabled - BEEP WHIR GYVE **
** :phase has been set to 'typer'. **
** scala.tools.nsc._ has been imported **
** global._, definitions._ also imported **
** Try :help, :vals, power.<tab> **
scala> def imps = intp.definedSymbolList map (_.fullName) mkString ("import ", "\nimport ", "\n")
imps: String
scala> import tools.reflect._
import tools.reflect._
scala> val tb = reflect.runtime.currentMirror.mkToolBox()
tb: scala.tools.reflect.ToolBox[reflect.runtime.universe.type] = scala.tools.reflect.ToolBoxFactory$ToolBoxImpl#24e15d95
Did I mention the import mechanism is awkward?
scala> val a = new A
a: A = A#1e5b2860
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
res0: Any = A
scala> ShowA show (a: Base)
res1: String = Base
scala> tb.eval(tb.parse(s"$imps ; ShowA show (a: Base)"))
res2: Any = Base
scala> val a: Base = new A
a: Base = A#7e3a93ce
scala> tb.eval(tb.parse(s"$imps ; ShowA show a"))
scala.tools.reflect.ToolBoxError: reflective compilation has failed:
reference to a is ambiguous;
it is imported twice in the same scope by
import a
and import a
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.throwIfErrors(ToolBoxFactory.scala:315)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.wrapInPackageAndCompile(ToolBoxFactory.scala:197)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal.compile(ToolBoxFactory.scala:251)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$compile$2.apply(ToolBoxFactory.scala:428)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$$anonfun$compile$2.apply(ToolBoxFactory.scala:421)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.liftedTree2$1(ToolBoxFactory.scala:354)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$withCompilerApi$.apply(ToolBoxFactory.scala:354)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.compile(ToolBoxFactory.scala:421)
at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl.eval(ToolBoxFactory.scala:443)
... 37 elided
So, if you decide what type you want to select on:
scala> val x: Base = new A
x: Base = A#2647e550
scala> tb.eval(tb.parse(s"$imps ; ShowA show x"))
res4: Any = Base
scala> tb.eval(tb.parse(s"$imps ; ShowA show (x.asInstanceOf[A])"))
res5: Any = A
It's not an answer to your question, looks more like a workaround:
abstract class Base {}
class A extends Base {}
class B extends Base {}
trait Show[T] {
def show(obj: T): String
}
class ShowBase extends Show[Base] {
override def show(obj: Base): String = "Base"
}
object ShowA extends Show[A] {
override def show(obj: A): String = "A"
}
object ShowB extends Show[B] {
override def show(obj: B): String = "B"
}
case class ^[T <: Base](obj: T, show: Show[T])
Seq(^(new A(), ShowA), ^(new B(), ShowB)).foreach {
case ^(obj, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
}
I had a fundamental error in thinking that overloaded methods are called based on dynamic binding (If you are wondering, the experience was like discovering that 2+2 is 5 instead of 4).
Thanks to som-snytt's answer and to a blog post about static and dynamic binding in Java I figured out that that is not the case. Overloaded methods are called based on static types. Overridden methods are called based on dynamic types. Hence the problem in my original question is based on static binding: som-snytt's answer explains that in more detail.