How does Scala use explicit types when resolving i

2019-06-17 02:11发布

问题:

I have the following code which uses spray-json to deserialise some JSON into a case class, via the parseJson method.

Depending on where the implicit JsonFormat[MyCaseClass] is defined (in-line or imported from companion object), and whether there is an explicit type provided when it is defined, the code may not compile.

I don't understand why importing the implicit from the companion object requires it to have an explicit type when it is defined, but if I put it inline, this is not the case?

Interestingly, IntelliJ correctly locates the implicit parameters (via cmd-shift-p) in all cases.

I'm using Scala 2.11.7.

Broken Code - Wildcard import from companion object, inferred type:

import SampleApp._
import spray.json._

class SampleApp {
  import MyJsonProtocol._
  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])

  object MyJsonProtocol extends DefaultJsonProtocol {
    implicit val myCaseClassSchemaFormat = jsonFormat1(MyCaseClass)
  }
}

Results in:

Cannot find JsonReader or JsonFormat type class for SampleAppObject.MyCaseClass

Note that the same thing happens with an explicit import of the myCaseClassSchemaFormat implicit.

Working Code #1 - Wildcard import from companion object, explicit type:

Adding an explicit type to the JsonFormat in the companion object causes the code to compile:

import SampleApp._
import spray.json._

class SampleApp {
  import MyJsonProtocol._
  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])

  object MyJsonProtocol extends DefaultJsonProtocol {
    //Explicit type added here now
    implicit val myCaseClassSchemaFormat: JsonFormat[MyCaseClass] = jsonFormat1(MyCaseClass)
  }
}

Working Code #2 - Implicits inline, inferred type:

However, putting the implicit parameters in-line where they are used, without the explicit type, also works!

import SampleApp._
import spray.json._

class SampleApp {
  import DefaultJsonProtocol._

  //Now in-line custom JsonFormat rather than imported
  implicit val myCaseClassSchemaFormat = jsonFormat1(MyCaseClass)

  val inputJson = """{"children":["a", "b", "c"]}"""
  println(s"Deserialise: ${inputJson.parseJson.convertTo[MyCaseClass]}")
}

object SampleApp {
  case class MyCaseClass(children: List[String])
}

回答1:

After searching for the error message Huw mentioned in his comment, I was able to find this StackOverflow question from 2010: Why does this explicit call of a Scala method allow it to be implicitly resolved?

This led me to this Scala issue created in 2008, and closed in 2011: https://issues.scala-lang.org/browse/SI-801 ('require explicit result type for implicit conversions?')

Martin stated:

I have implemented a slightly more permissive rule: An implicit conversion without explicit result type is visible only in the text following its own definition. That way, we avoid the cyclic reference errors. I close for now, to see how this works. If we still have issues we migth come back to this.

This holds - if I re-order the breaking code so that the companion object is declared first, then the code compiles. (It's still a little weird!)

(I suspect I don't see the 'implicit method is not applicable here' message because I have an implicit value rather than a conversion - though I'm assuming here that the root cause is the same as the above).