简体   繁体   中英

Scala bug? DelayedInit and Implicits

I've found extremely weird behaviour (scala 2.9.1 ) using and defining implicit values, and wondering if anyone can explain it, or if it's a scala bug?

I've created a self contained example:

object AnnoyingObjectForNoPurpose {

  trait Printer[T] {
    def doPrint(v: T): Unit
  }

  def print[T : Printer](v: T) = implicitly[Printer[T]].doPrint(v)


  trait DelayedRunner extends DelayedInit {
    def delayedInit(x: => Unit){ x }
  }

  // this works, as it should
  object Normal extends DelayedRunner {
      implicit val imp = new Printer[Int] {
        def doPrint(v: Int) = println(v + " should work")
      }

      print(343)
  }

  // this compiles, but it shouldn't
  // and won't run, cause the implicit is still null
  object FalsePositive extends DelayedRunner {

      print(123)

      implicit val imp = new Printer[Int] {
        def doPrint(v: Int) = println(v + " should not compile")
      }
  }

  def main(args: Array[String]) {
    implicit val imp = new Printer[Int] {
      def doPrint(v: Int) = println(v + " should work")
    }

    print(44)
    // print(33.0) // correctly doesn't work 

    Normal // force it to run
    FalsePositive // force this to run too 
  }
}

Suppose you changed your definition of delayInit to be a no-op, ie

def delayedInit(x: => Unit) {  }

Then in your main method do something like

println("FP.imp: " + FalsePositive.imp)

As expected that will print FP.imp: null , but the real point of the exercise is to illustrate that the block that defines the body of FalsePositive is acting like a regular class body, not a function body. It's defining public members when it sees val , not local variables.

If you added a method to AnnoyingObjectForNoPurpose like the following, it wouldn't compile because print 's implicit requirement isn't satisfied.

def fails {
  print(321)
  implicit val cantSeeIt = new Printer[Int] {
    def doPrint(v: Int) = println(v + " doesn't compile")
  }
}

However if you defined a class along the same principle, it would compile, but fail at runtime when initialized, just like your FalsePositive example.

class Fine {
  print(321)
  implicit val willBeNull = new Printer[Int] {
    def doPrint(v: Int) = println(v + " compiles, but fails")
  }
}

To be clear, the compile behavior of Fine has nothing to do with the presence of the implicit . Class/object initializers are very happy to compile with val initializers which reference undefined val s.

object Boring {
  val b = a
  val a = 1
  println("a=%s b=%s".format(a, b))
}

Boring compiles just fine and when it is referenced, it prints a=1 b=0

It seems like your question boils down to "Should the body of a class/object deriving from DelayedInit be compiled as if it's a class body or a function block?"

It looks like Odersky picked the former, but you're hoping for the latter.

That's the same bug as if I write this:

object Foo {
   println(x)
   val x = 5
}

It ain't a bug. Your constructor flows down the object body and happens in order. DelayedInit isn't the cause. This is why you need to be careful when using val/vars and ensure they init first. This is also why people use lazy val's to resolve initialization order issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM