Scala - 为什么不根据运行时类调用重载方法?

时间:2014-04-15 18:34:34

标签: scala types overloading

问题

给出一个简单的类层次结构

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 ShowBase {
  def show(obj: A): String = "A"
}
object ShowB extends ShowBase {
  def show(obj: B): String = "B"
}

执行以下测试用例

Seq((new A(), ShowA), (new B(), ShowB)).foreach {
  case (obj, showImpl) => println(showImpl.show(obj), obj.getClass.getSimpleName)
}

应生成(A,A) \n (B,B),但会生成(Base,A) \n (Base,B)

问题

这里发生了什么?不应该调用具有最特定运行时类型的方法 - Polymorphism 101吗?

此问题与another question类似,其中类型参数阻止正确解析要调用的方法。但是,在我的情况下,类型参数化show方法提供了实际实现,与另一个问题中的类型参数化方法相反。

天真的解决方案

扩展ShowA实施(类似于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])
  }
}

给出预期的输出。问题是将AShowB混合会导致异常

3 个答案:

答案 0 :(得分:3)

静态重载解析很容易解释:对于适用的方法,只根据签名选择一种方法“更具体”。

然而,

scala> Seq((new A(), ShowA), (new B(), ShowB))
res0: Seq[(Base, ShowBase)] = List((A@2b45f918,ShowA$@7ee4acd9), (B@57101ba4,ShowB$@6286d8a3))
ShowBase中的

没有过载。

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

哦,是的,不要使用Scala中的getSimpleName

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)

很容易想象一个宏使用重载方法show为给定接口生成部分函数。

另一个想法,不一定是伟大的想法,是让编译器在运行时进行选择。

目前在REPL中证明这一点很难。您必须从丢失REPL历史记录的对象中导入要使用的任何符号。 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
嘿,它有效!

或者,进入电源模式,它将当前阶段设置为typer,并为您提供intp没有时髦的美元符号。因为我们真的需要更多美元吗?

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

如果您想查看未经过抽样的导入:

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]

再一次:

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

我提到导入机制是不是很尴尬?

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

因此,如果您决定要选择的类型:

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

答案 1 :(得分:1)

这不是你问题的答案,看起来更像是一种解决方法:

  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)
  }

答案 2 :(得分:0)

我认为基于动态绑定调用重载方法时出现了一个根本性的错误(如果你想知道,经验就像发现2 + 2是5而不是4)。

感谢som-snytt's answerblog post about static and dynamic binding in Java,我发现事实并非如此。 基于静态类型调用重载方法。基于动态类型调用重写方法。因此,我原始问题中的问题基于静态绑定:som-snytt's answer更详细地解释了这一点。