compiler failure to resolve Implicit types with bounds (sometimes)

空扰寡人 提交于 2019-12-11 13:27:17

问题


Edit:

Last revision was deemed unhelpful as it did not contain necessary information that help narrow down my issue. hence the need to also include the AST.

Below is a library in its entirety that allows parsing and writing of play-json's json based on user defined schema; Similar to what Scala's slick offers for database columns to some extent:

import scala.language.higherKinds
import play.api.libs.functional.syntax._
import play.api.libs.json._

import scala.language.{higherKinds, implicitConversions}

type PathNodes = List[PathNode]

sealed trait Field[A] {

  def pathNodes: PathNodes

  def jsPath: JsPath = JsPath(pathNodes)

  def relativePath: JsPath = JsPath(List(pathNodes.last))

  def format: Format[A]

  def nestedFormatter(path: JsPath): OFormat[A]

  def nestedFormat: OFormat[A] = nestedFormatter(relativePath)
}

case class PlainField[A: Format](prefix: PathNodes) extends Field[A] {

  override def pathNodes: PathNodes = prefix

  def format: Format[A] = implicitly[Format[A]]

  override def nestedFormatter(path: JsPath): OFormat[A] = path.format(format)
}

abstract class JsonSchema[T](val _prefix: PathNodes) extends Field[T] with SchemaExtensionMethods {

  override def pathNodes: PathNodes = _prefix

  def format: OFormat[T]

  protected def plain[A: Format](name: String): PlainField[A] = PlainField[A](_prefix :+ KeyPathNode(name))

  protected def nested[N](name: String, factory: PathNodes => N): N = factory(_prefix :+ KeyPathNode(name))

  protected def nested[B, G <: JsonSchema[B]](name: String)(implicit sm: HasJsonSchema[B, G]): G = sm.apply(_prefix :+ KeyPathNode(name))

  override def nestedFormatter(path: JsPath): OFormat[T] = path.format(format)
}

case class Optional[F, A](field: F)(implicit ev: F <:< Field[A]) extends Field[Option[A]] {

  override def pathNodes: PathNodes = field.pathNodes

  override def format: Format[Option[A]] = {
    implicit val writes: Writes[Option[A]] = JsPath.writeNullable(field.format)
    implicit val reads: Reads[Option[A]] = JsPath.readNullable(field.format)
    implicitly[Format[Option[A]]]
  }

  def map[G, B](f: F => G)(implicit ev: G <:< Field[B]): Optional[G, B] = new Optional[G, B](f(field))

  def flatMap[G <: Field[B], B](f: F => Optional[G, B]): Optional[G, B] = f(field)

  override def nestedFormatter(path: JsPath): OFormat[Option[A]] = path.formatNullable(field.format)
}

case class Collection[F, A](field: F)(implicit ev: F <:< Field[A], repath: Repath[F]) extends Field[Seq[A]] {
  override def pathNodes: PathNodes = field.pathNodes

  override def format: Format[Seq[A]] = {
    implicit val writes: Writes[Seq[A]] = Writes.seq(field.format)
    implicit val reads: Reads[Seq[A]] = Reads.seq(field.format)
    implicitly[Format[Seq[A]]]
  }

  def apply(idx: Int): F = implicitly[Repath[F]].apply(field, IdxPathNode(idx))

  override def nestedFormatter(path: JsPath): OFormat[Seq[A]] = path.format(format)
}

class FormatExtensionMethods[T](val arg: T) {
  def <>[A, B, Fun](apply: Fun, unapply: B => Option[A])(implicit jss: JsonShape[A, B, T, Fun]): OFormat[B] = jss.format(arg, apply, unapply andThen (_.get))
}

class FieldExtensionMethods[F](val field: F) {
  def optional[A](implicit ev: F <:< Field[A]): Optional[F, A] = new Optional[F, A](field)

  def sequence[A](implicit ev: F <:< Field[A], repath: Repath[F]): Collection[F, A] = new Collection[F, A](field)
}

trait SchemaExtensionMethods {
  implicit def formatExtensionMethods[M](t: M): FormatExtensionMethods[M] = new FormatExtensionMethods[M](t)

  implicit def fieldExtensionMethods[M, A](t: M): FieldExtensionMethods[M] = new FieldExtensionMethods[M](t)
}

trait Repath[F] {
  def apply(f: F, node: PathNode): F
}

object Repath {
  implicit def plain[T]: Repath[PlainField[T]] = new Repath[PlainField[T]] {
    override def apply(t: PlainField[T], node: PathNode): PlainField[T] =
      PlainField[T](t.pathNodes :+ node)(t.format)
  }

  implicit def schema[S <: JsonSchema[_]](implicit sm: HasJsonSchema[_, S]): Repath[S] = new Repath[S] {
    override def apply(t: S, node: PathNode): S =
      sm.apply(t.pathNodes :+ node)
  }

  implicit def option[F <: Field[T] : Repath, T]: Repath[Optional[F, T]] = new Repath[Optional[F, T]] {
    override def apply(t: Optional[F, T], node: PathNode): Optional[F, T] =
      new Optional[F, T](implicitly[Repath[F]].apply(t.field, node))
  }

  implicit def sequence[F <: Field[T] : Repath, T]: Repath[Collection[F, T]] = new Repath[Collection[F, T]] {
    override def apply(t: Collection[F, T], node: PathNode): Collection[F, T] =
      new Collection[F, T](implicitly[Repath[F]].apply(t.field, node))
  }
}

trait JsonShape[A, B, -T, Func] {
  def format(t: T, apply: Func, unapply: B => A): OFormat[B]
}

object JsonShape {
  type F[T] = Field[T]

  implicit def cc1[A, B]: JsonShape[A, B, F[A], (A) => B] = (t: F[A], apply: (A) => B, unapply: B => A) => {
    val name = t.pathNodes.last.asInstanceOf[KeyPathNode].key
    OFormat[B](
      Reads[B](jsv => (jsv \ name).validate[A](t.format).map(apply)),
      OWrites[B](b => JsObject(Map(name -> Json.toJson(unapply(b))(t.format))))
    )
  }

  implicit def cc2[T1, T2, B]: JsonShape[(T1, T2), B, (F[T1], F[T2]), (T1, T2) => B] = (t: (F[T1], F[T2]), apply: (T1, T2) => B, unapply: B => (T1, T2)) => {
    (
      t._1.nestedFormat and
        t._2.nestedFormat
      ) (apply, unapply)
  }

  implicit def cc3[T1, T2, T3, B]: JsonShape[(T1, T2, T3), B, (F[T1], F[T2], F[T3]), (T1, T2, T3) => B] = (t: (F[T1], F[T2], F[T3]), apply: (T1, T2, T3) => B, unapply: B => (T1, T2, T3)) => {
    (
      t._1.nestedFormat and
        t._2.nestedFormat and
        t._3.nestedFormat
      ) (apply, unapply)
  }

  //this goes up to 22

}

abstract class HasJsonSchema[T, +S <: JsonSchema[T]](val apply: PathNodes => S) extends OFormat[T] {
  val root: S = apply(Nil)

  def format: OFormat[T] = root.format

  def writes(o: T): JsObject = root.format.writes(o)

  def reads(json: JsValue): JsResult[T] = root.format.reads(json)
}

Now let's write a small piece of client code that reproduce the issue:

case class MessageSchema(prefix: PathNodes) extends JsonSchema[Message](prefix) {

  def underlying = plain[String]("underlying")
  //def underlying = plain[String]("underlying").optional if I wanted the field to be Option[String]
  //def underlying = plain[String]("underlying").sequence if I wanted the field to be Seq[String]
  override def format = underlying <> (Message.apply _, Message.unapply)

}

case class Message(underlying: String)

object Message {

  implicit object sm extends HasJsonSchema[Message, MessageSchema](MessageSchema.apply)

}

case class LanguageTaggedSchema[T, S <: JsonSchema[T]](prefix: PathNodes)(implicit evT: HasJsonSchema[T, S]) extends JsonSchema[LanguageTagged[T]](prefix) {
  def lang = plain[String]("lang")

  def data: S = nested("data")(evT)

  def format = (lang, data) <> (LanguageTagged.apply[T] _, LanguageTagged.unapply[T])
}

case class LanguageTagged[T](lang: String, data: T)

object LanguageTagged {

  implicit def schemaMapper[T, S <: JsonSchema[T]](implicit ev: HasJsonSchema[T, S]): HasJsonSchema[LanguageTagged[T], LanguageTaggedSchema[T, S]] =
    new HasJsonSchema[LanguageTagged[T], LanguageTaggedSchema[T, S]](LanguageTaggedSchema.apply[T, S]) {}
}

def toJson[T, S <: JsonSchema[T]](a: T)(implicit ev: HasJsonSchema[T, S]): JsValue = Json.toJson(a)(ev.format)

toJson(Message("hi")) //Ok!
toJson(LanguageTagged("en", Message("hi"))) //Ok!
//or simply write
Json.toJson(LanguageTagged("en", Message("hi")))

//and if i wanted to traverse a json path i would do:
val schema = implicitly[HasJsonSchema[LanguageTagged[Message],LanguageTaggedSchema[Message,MessageSchema]]].root
schema.data.underlying.jsPath
//prints: res2: play.api.libs.json.JsPath = /data/underlying

//Now to where the problem starts:

def getSchema[T, S <: JsonSchema[T]](a: T)(implicit ev: HasJsonSchema[T, S]): S = ev.root

getSchema(Message("hi")) //Ok!
getSchema(LanguageTagged("en", Message("hi"))) //Not Ok but why?
//Error:(211, 11) could not find implicit value for
//parameter ev: A$A6.this.HasJsonSchema[A$A6.this.LanguageTagged[A$A6.this.Message],S]
//getSchema(LanguageTagged("en", Message("hi")));//
//^

I have a huge suspicion that the compiler runs into issues because of the bounded type of S inHasJsonSchema[T, S <: JsonSchema[T]] when infering the implicit type S. and so far only in that specific situation as shown on the last line of all the code. as a dubugging attempt I created a similar situation and realized that if the type S was not bounded I wouldn't have this issue. Any sort of solution that refactors the code such that it doesn't depend on bounded types or one that simply solves the implicit resolution is appreciated


回答1:


What You're trying to achieve cannot be done with subtyping. You should use type-classes instead, a more in-depth explanation:

http://danielwestheide.com/blog/2013/02/06/the-neophytes-guide-to-scala-part-12-type-classes.html



来源:https://stackoverflow.com/questions/47707034/compiler-failure-to-resolve-implicit-types-with-bounds-sometimes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!