问题
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