【问题标题】:Why these implicit conversions resulted in looping code为什么这些隐式转换会导致代码循环
【发布时间】:2012-01-13 10:57:29
【问题描述】:

考虑 Scala 中的以下代码:

object Test {
  class A {}

  class B extends A {}

  class AI extends A {
    def sayHello: String = "Hello from AI"
  }

  implicit def AtoAI(a: A): AI = a

  class BI extends B {
    def sayHello: String = "Hello from BI"
  }

  implicit def BtoBI(b: B): BI = b

  def main(args: Array[String]) {
    val a = new A
    println(a.sayHello)

    val b = new B
    println(b.sayHello)
  }
}

使用隐式会导致代码循环。其实反汇编发现,生成的转换方法里面只有一个goto 0

public Test$AI AtoAI(Test$A);
  Code:
   0:   goto    0

public Test$BI BtoBI(Test$B);
  Code:
   0:   goto    0

是什么导致了这种行为?我知道,这里的类层次结构是可疑的,但隐式转换应该只应用一次。

我使用 Scala 2.9.1

【问题讨论】:

  • 我不明白为什么代码会编译(我验证它确实编译)。难道你不用在隐式转换方法中向下转换吗?
  • 其实我也没有。这是与大部分代码隔离的情况。

标签: scala implicit


【解决方案1】:

不好,但我绝对不会称其为错误。

归结为

class A

class B

implicit def aToB(a: A) : B = a

转换的两侧无需以任何方式关联。隐含和写作是一样的

implicit def aToB(a: A): B = aToB(a)

因为编译器插入了aToB 调用,以便将结果a 转换为所需的返回类型B

goto 0 实现只是尾调用优化。编译器在生成以这种方式启动的方法时可能会发出警告。

也许有一条规则,隐式方法不能作为它们自己体内的隐式方法使用。但它并不总是造成无限循环

implicit def listAToListB(l: list[A] = l match {
  case Nil => Nil
  case x:xs => toB(x) :: xs // equivalent to toB(x) :: listAToList[B](xs)
}

(好吧,这只是一个map(toB))。无论如何,两个相互递归的隐式可能会发生同样的情况。在我看来,为了避免编写一个无限的、什么都不做的循环等等的可能性而调整规范是不值得的。但是,当检测到这样的循环时发出警告,无论隐含如何,都会很好。

【讨论】:

    【解决方案2】:

    我完全不明白为什么代码会编译。我验证它确实可以编译。在隐式转换方法里面不用向下转型吗?

    Didier 指出,隐式转换是递归应用的,这意味着代码无需向下转换即可编译。

    以下代码添加了向下转换(这不应该改变运行时行为)。它在运行时因强制转换失败而失败。 所以这在我看来像是一个编译器错误。 正如 Didier 所说,显式向下转换阻止了此处隐式转换的递归应用。

    object Test {
      class A {}
    
      class B extends A {}
    
      class AI extends A {
        def sayHello: String = "Hello from AI"
      }
    
      implicit def AtoAI(a: A): AI = a.asInstanceOf[AI]
    
      class BI extends B {
        def sayHello: String = "Hello from BI"
      }
    
      implicit def BtoBI(b: B): BI = b.asInstanceOf[BI]
    
      def main(args: Array[String]) {
        val a = new A
        println(a.sayHello)
    
        val b = new B
        println(b.sayHello)
      }
    }
    

    通过回答问题来结束:您在没有此方法的A 类的对象上调用AI 的方法。显然这是行不通的。发生的事情是未指定的;在你的情况下,这是一个无限循环。

    【讨论】:

    • 添加(失败的)向下转换确实会改变行为,因为使用向下转换,编译器不会尝试插入隐式转换。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-30
    • 2013-06-17
    • 1970-01-01
    • 2012-11-18
    • 1970-01-01
    相关资源
    最近更新 更多