Related
I would like to have some function applied to fields in a case class, that are annotated with MyAnnotation. The idea is to transform type T into its generic representation, extract annotations, zip, fold right (or left) to reconstruct a generic representation and finally get back to type T. I followed the answer provided here and this gist.
I'm using scala 2.11.12 and shapeless 2.3.3.
Hereafter is my code:
import shapeless._
import shapeless.ops.hlist._
case class MyAnnotation(func: String) extends scala.annotation.StaticAnnotation
trait Modifier[T] {
def modify(t: T): T
}
object Modifier {
def apply[A: Modifier]: Modifier[A] = implicitly[Modifier[A]]
def create[T](func: T => T): Modifier[T] = new Modifier[T] { override def modify(t: T): T = func(t) }
private def id[T](t: T) = t
implicit val stringModifier: Modifier[String] = create(id)
implicit val booleanModifier: Modifier[Boolean] = create(id)
implicit val byteModifier: Modifier[Byte] = create(id)
implicit val charModifier: Modifier[Char] = create(id)
implicit val doubleModifier: Modifier[Double] = create(id)
implicit val floatModifier: Modifier[Float] = create(id)
implicit val intModifier: Modifier[Int] = create(id)
implicit val longModifier: Modifier[Long] = create(id)
implicit val shortModifier: Modifier[Short] = create(id)
implicit val hnilModifier: Modifier[HNil] = create(id)
implicit def hlistModifier[H, T <: HList, AL <: HList](
implicit
hser: Lazy[Modifier[H]],
tser: Modifier[T]
): Modifier[H :: T] = new Modifier[H :: T] {
override def modify(ht: H :: T): H :: T = {
ht match {
case h :: t =>
hser.value.modify(h) :: tser.modify(t)
}
}
}
implicit val cnilModifier: Modifier[CNil] = create(id)
implicit def coproductModifier[L, R <: Coproduct](
implicit
lser: Lazy[Modifier[L]],
rser: Modifier[R]
): Modifier[L :+: R] = new Modifier[L :+: R] {
override def modify(t: L :+: R): L :+: R = t match {
case Inl(l) => Inl(lser.value.modify(l))
case Inr(r) => Inr(rser.modify(r))
}
}
object Collector extends Poly2 {
implicit def myCase[ACC <: HList, E] = at[(E, Option[MyAnnotation]), ACC] {
case ((e, None), acc) => e :: acc
case ((e, Some(MyAnnotation(func))), acc) => {
println(func)
e :: acc
}
}
}
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
rightFolder: RightFolder[ZL, HNil.type, Collector.type]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
val modified = zipped.foldRight(HNil)(Collector)
println(modified)
val typed = gen.from(generic) // temporary
typed
}
}
}
The code above compiles. However, when instanciating a Modifier in a test:
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = implicitly
the test file does not compile and give the following error:
[error] ambiguous implicit values:
[error] both value StringCanBuildFrom in object Predef of type =>
scala.collection.generic.CanBuildFrom[String,Char,String]
[error] and method $conforms in object Predef of type [A]=> <:<[A,A]
[error] match expected type T
[error] val ser1: Modifier[Test] = implicitly
The problem seems to come from the right folder definition: when removing rightFolder from the list of implicits in genericModifier, then it works:
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](
implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL]/*,
rightFolder: RightFolder[ZL, HNil.type, Collector.type]*/
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
/*val modified = zipped.foldRight(HNil)(Collector)
println(modified)*/
val typed = gen.from(generic) // temporary
typed
}
}
What is wrong?
There are several mistakes in your code:
defining Poly just for Option is too rough (pattern matching is performed at runtime and compiler should know definitions for Some and None at compile time)
HNil should be instead of HNil.type and HNil : HNil instead of HNil (types HNil and HNil.type are different)
compiler doesn't know that RightFolder actually returns the original HList type, so you should use RightFolder.Aux type.
Correct code is
import shapeless.ops.hlist.{RightFolder, Zip}
import shapeless.{::, Annotations, Generic, HList, HNil, Lazy, Poly2}
import scala.annotation.StaticAnnotation
object App {
case class MyAnnotation(func: String) extends StaticAnnotation
object Collector extends Poly2 {
// implicit def myCase[ACC <: HList, E] = at[(E, Option[PII]), ACC] {
// case ((e, None), acc) => e :: acc
// case ((e, Some(MyAnnotation(func))), acc) => {
// println(func)
// e :: acc
// }
// }
implicit def someCase[ACC <: HList, E]: Case.Aux[(E, Some[MyAnnotation]), ACC, E :: ACC] = at {
case ((e, Some(MyAnnotation(func))), acc) =>
println(func)
e :: acc
}
implicit def noneCase[ACC <: HList, E]: Case.Aux[(E, None.type), ACC, E :: ACC] = at {
case ((e, None), acc) => e :: acc
}
}
trait Modifier[T] {
def modify(t: T): T
}
implicit def hListModifier[HL <: HList]: Modifier[HL] = identity(_)
// added as an example, you should replace this with your Modifier for HList
implicit def genericModifier[T, HL <: HList, AL <: HList, ZL <: HList](implicit
gen: Generic.Aux[T, HL],
ser: Lazy[Modifier[HL]],
annots: Annotations.Aux[MyAnnotation, T, AL],
zip: Zip.Aux[HL :: AL :: HNil, ZL],
rightFolder: RightFolder.Aux[ZL, HNil/*.type*/, Collector.type, HL /*added*/]
): Modifier[T] = new Modifier[T] {
override def modify(t: T): T = {
val generic = gen.to(t)
println(generic)
val annotations = annots()
println(annotations)
val zipped = zip(generic :: annotations :: HNil)
println(zipped)
val modified = zipped.foldRight(HNil : HNil /*added*/)(Collector)
println(modified)
val typed = gen.from(modified)
typed
}
}
case class Test(a: String, #MyAnnotation("sha1") b: String)
val test = Test("A", "B")
val modifier: Modifier[Test] = implicitly[Modifier[Test]]
def main(args: Array[String]): Unit = {
val test1 = modifier.modify(test) // prints "sha1"
println(test1) // Test(A,B)
}
}
I'm trying to come up with something similar to Classy Lenses to use with cats-mtl. For this, I want to be able to construct a Lens based on provided types only. I found no way to do it using operations provided in shapeless, so I'm writing a new one.
import shapeless._
class Classy[O[_, _], S, A](val get: O[S, A])
object Classy {
def apply[O[_, _], S, A](implicit ev: Classy[O, S, A]): Classy[O, S, A] = ev
implicit def rootLens[S]: Classy[Lens, S, S] =
new Classy(OpticDefns.id[S])
implicit def elementLens[S, L <: HList, A](
implicit genLens: MkGenericLens.Aux[S, L],
aLens: MkHListSelectLens[L, A]
): Classy[Lens, S, A] = new Classy(aLens() compose genLens())
implicit def composeLens[S, A, T](
implicit lh: Lazy[Classy[Lens, S, A]],
rh: Classy[Lens, A, T]
): Classy[Lens, S, T] = new Classy(rh.get compose lh.value.get)
}
Unfortunately, the case I'm after is not compiling:
Classy[Lens, String, String] // OK
Classy[Lens, (Long, String), String] // OK
Classy.composeLens[(Int, (Long, String)), (Long, String), String] // OK, explicit call with explicit params
//Classy[Lens, (Int, (Long, String)), String] // <- doesn't compile
I tried a number of combinations with Lazy/ Strict / plain implicit, but none of these have worked.
Try the following approach with redefining operations making them work deeper:
import shapeless.{::, DepFn1, DepFn2, Generic, HList, HNil, Lens, OpticDefns}
trait DeepGeneric[T <: Product] {
type Repr <: HList
def to(t : T) : Repr
def from(r : Repr) : T
}
object DeepGeneric {
type Aux[T <: Product, Repr0 <: HList] = DeepGeneric[T] { type Repr = Repr0 }
def instance[T <: Product, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def deepGeneric[A <: Product, L <: HList, L1 <: HList](implicit
generic: Generic.Aux[A, L],
hListDeepGeneric: HListDeepGeneric.Aux[L, L1]
): Aux[A, L1] = instance(a => hListDeepGeneric.to(generic.to(a)), l1 => generic.from(hListDeepGeneric.from(l1)))
}
trait HListDeepGeneric[T <: HList] {
type Repr <: HList
def to(t : T) : Repr
def from(r : Repr) : T
}
trait LowPriorityHListDeepGeneric {
type Aux[T <: HList, Repr0 <: HList] = HListDeepGeneric[T] { type Repr = Repr0 }
def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepGeneric[T] {
override type Repr = Repr0
override def to(t: T): Repr = f(t)
override def from(r: Repr): T = g(r)
}
implicit def headNotCaseClass[H, T <: HList, T_hListDeepGen <: HList](implicit
tailHListDeepGeneric: HListDeepGeneric.Aux[T, T_hListDeepGen]
): Aux[H :: T, H :: T_hListDeepGen] = instance({
case h :: t => h :: tailHListDeepGeneric.to(t)
}, {
case h :: t => h :: tailHListDeepGeneric.from(t)
})
}
object HListDeepGeneric extends LowPriorityHListDeepGeneric {
implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)
implicit def headCaseClass[H <: Product, T <: HList, H_deepGen <: HList, T_hListDeepGen <: HList](implicit
headDeepGeneric: DeepGeneric.Aux[H, H_deepGen],
tailHListDeepGeneric: HListDeepGeneric.Aux[T, T_hListDeepGen]
): Aux[H :: T, H_deepGen :: T_hListDeepGen] = instance({
case h :: t => headDeepGeneric.to(h) :: tailHListDeepGeneric.to(t)
}, {
case h :: t => headDeepGeneric.from(h) :: tailHListDeepGeneric.from(t)
})
}
// example
case class A(i: Int, b: Boolean)
case class B(s: String, d: Double)
case class C(a: A, b: B, l: Long)
implicitly[DeepGeneric.Aux[C, (Int :: Boolean :: HNil) :: (String :: Double :: HNil) :: Long :: HNil]]
trait DeepSelector[L <: HList, U] extends DepFn1[L] { type Out = U }
trait LowPriorityDeepSelector {
def instance[L <: HList, U](f: L => U): DeepSelector[L, U] = (l: L) => f(l)
implicit def select[H, T <: HList]: DeepSelector[H :: T, H] = instance(_.head)
}
object DeepSelector extends LowPriorityDeepSelector {
implicit def deepSelect[H <: HList, T <: HList, H_deepGen <: HList, U](implicit
deepSelector: DeepSelector[H, U]
): DeepSelector[H :: T, U] =
instance(l => deepSelector(l.head))
implicit def recurse[H, T <: HList, U](implicit deepSelector: DeepSelector[T, U]): DeepSelector[H :: T, U] =
instance(l => deepSelector(l.tail))
}
trait DeepReplacer[L <: HList, U, V] extends DepFn2[L, V]
trait LowPriorityDeepReplacer {
type Aux[L <: HList, U, V, Out0] = DeepReplacer[L, U, V] { type Out = Out0 }
def instance[L <: HList, U, V, Out0](f: (L, V) => Out0): Aux[L, U, V, Out0] = new DeepReplacer[L, U, V] {
override type Out = Out0
override def apply(l: L, v: V): Out = f(l, v)
}
implicit def replace[T <: HList, U, V]: Aux[U :: T, U, V, (U, V :: T)] = instance((l, v) => (l.head, v :: l.tail))
}
object DeepReplacer extends LowPriorityDeepReplacer {
implicit def deepReplace[H <: HList, T <: HList, U, V, H1 <: HList](implicit
deepReplacer: Aux[H, U, V, (U, H1)]): Aux[H :: T, U, V, (U, H1 :: T)] = instance((l, v) => {
val (u, h1) = deepReplacer(l.head, v)
(u, h1 :: l.tail)
})
implicit def recurse[H, T <: HList, U, V, OutT <: HList](implicit
deepReplacer : Aux[T, U, V, (U, OutT)]): Aux[H :: T, U, V, (U, H :: OutT)] = instance((l, v) => {
val (u, l1) = deepReplacer(l.tail, v)
(u, l.head :: l1)
})
}
trait MkDeepGenericLens[T] {
type Repr
def apply(): Lens[T, Repr]
}
object MkDeepGenericLens {
type Aux[T, Repr0] = MkDeepGenericLens[T] { type Repr = Repr0 }
def instance[T, Repr0](f: => Lens[T, Repr0]): Aux[T, Repr0] = new MkDeepGenericLens[T] {
override type Repr = Repr0
override def apply(): Lens[T, Repr] = f
}
implicit def mkDeepGenericLens[T <: Product](implicit gen: DeepGeneric[T]): Aux[T, gen.Repr] =
instance(new Lens[T, gen.Repr] {
def get(t: T): gen.Repr = gen.to(t)
def set(t: T)(r: gen.Repr): T = gen.from(r)
})
}
trait MkHListDeepSelectLens[L <: HList, U] {
def apply(): Lens[L, U]
}
object MkHListDeepSelectLens {
def instance[L <: HList, U](f: => Lens[L, U]): MkHListDeepSelectLens[L, U] = () => f
implicit def mKHlistDeepSelectLens[L <: HList, U](implicit
selector: DeepSelector[L, U], replacer: DeepReplacer.Aux[L, U, U, (U, L)]): MkHListDeepSelectLens[L, U] =
instance(new Lens[L, U] {
def get(l: L) = selector(l)
def set(l: L)(u: U): L = replacer(l, u)._2
})
}
class Classy[O[_, _], S, A](val get: O[S, A])
object Classy {
def apply[O[_, _], S, A](implicit ev: Classy[O, S, A]): Classy[O, S, A] = ev
implicit def rootLens[S]: Classy[Lens, S, S] = new Classy(OpticDefns.id[S])
implicit def elementLens[S, L <: HList, A](implicit
genLens: MkDeepGenericLens.Aux[S, L],
aLens: MkHListDeepSelectLens[L, A]
): Classy[Lens, S, A] = new Classy(aLens() compose genLens())
}
Classy[Lens, String, String] // OK
Classy[Lens, (Long, String), String] // OK
Classy[Lens, (Int, (Long, String), Double), String] // OK
Motivated by example https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/deephlister.scala
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
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
I am trying to do a play json reads from arbitrary case class using shapeless.
For the moment I'am trying to implement the following steps
From T, I have a FieldType[K1, V1] :: FieldType[K2, V2] :: ... using LabelledGeneric
Then I want to build an HList of type Reads[V1] :: Reads[V2] ...
Here is the code I'am using :
/*
* To build the json reads from T
*/
trait HReads[PRepr <: HList] {
type Out
def reads: Out
}
object HReads {
type Aux[PRepr <: HList, Out1 <: HList] = HReads[PRepr] { type Out = Out1 }
implicit def readsHNil(): Aux[HNil, HNil] = new HReads[HNil] {
type Out = HNil
override def reads: Out = {
throw new RuntimeException("Oups")
}
}
implicit def readsSingleton[T, K <: Symbol](
implicit
kWitness: Witness.Aux[K],
jsReads: play.api.libs.json.Reads[T]
): Aux[FieldType[K, T] :: HNil, Reads[T] :: HNil] = new HReads[FieldType[K, T] :: HNil] {
type Out = Reads[T] :: HNil
override def reads: Out = {
val name: String = kWitness.value.name
val pathReads: Reads[T] = (__ \ name).read[T](jsReads)
pathReads :: HNil
}
}
implicit def readsStd[T, K <: Symbol, RestRepr <: HList, Rest <: HList](
implicit
kWitness: Witness.Aux[K],
jsReads: Reads[T],
hreads: Lazy[HReads.Aux[RestRepr, Rest]]
): Aux[FieldType[K, T] :: RestRepr, Reads[T] :: Rest] = new HReads[FieldType[K, T] :: RestRepr] {
type Out = Reads[T] :: Rest
override def reads: Out = {
val name: String = kWitness.value.name
val pathReads: Reads[T] = (__ \ name).read[T](jsReads)
val value: Rest = hreads.value.reads
pathReads :: value
}
}
def jsonReads[P]: JsonReads[P] = new JsonReads[P] {}
implicit class JsonReadsOps[In](in: JsonReads[In]) {
def jsonReads[K <: Symbol, T, InRepr <: HList, HR <: HList]()(
implicit
gen: LabelledGeneric.Aux[In, FieldType[K, T] :: InRepr],
hreads: HReads.Aux[FieldType[K, T] :: InRepr, Reads[T] :: HR]
): Reads[T] :: HR = {
hreads.reads
}
}
}
// And trying to use this like that :
import HReads._
implicit val l = LabelledGeneric[MonPojo]
private val allReads = jsonReads[MonPojo].jsonReads()
println(s"All Reads $allReads")
//[error] validation\validation.scala:428: could not find implicit value for parameter hreads: validation.validations.HReads.Aux[shapeless.labelled.FieldType[K,T] :: InRepr,play.api.libs.json.Reads[T] :: HR]
//[error] private val allReads = jsonReads[MonPojo].jsonReads()
//[error] ^
//[error] one error found
Is someone could help me ?
Thanks Alex.
Here is implementation of ReadsWithRules:
trait ReadsWithRules[T, R <: HList] {
def withRules(rules: R): Reads[T]
}
trait ReadsWithRulesLowerPriority {
implicit def readsNoRule[T](implicit reads: Reads[T]): ReadsWithRules[T, HNil] = new ReadsWithRules[T, HNil] {
override def withRules(rules: HNil): Reads[T] = reads
}
implicit def readsGeneric[Repr, A, R <: HList](implicit
gen: LabelledGeneric.Aux[A, Repr],
readsRepr: Lazy[ReadsWithRules[Repr, R]]
): ReadsWithRules[A, R] =
new ReadsWithRules[A, R] {
override def withRules(rules: R): Reads[A] = {
readsRepr.value.withRules(rules).map(r => gen.from(r))
}
}
}
object ReadsWithRules extends ReadsWithRulesLowerPriority {
implicit def readHNil[R <: HList]: ReadsWithRules[HNil, R] = new ReadsWithRules[HNil, R] {
override def withRules(rules: R): Reads[HNil] = implicitly[Reads[HNil]]
}
implicit def readNoRuleForHead[K <: Symbol, H, T <: HList, R <: HList](implicit
witness: Witness.Aux[K],
noRule: LacksKey[R, K],
readsH: Reads[H],
readsT: ReadsWithRules[T, R]
): ReadsWithRules[FieldType[K, H] :: T, R] =
new ReadsWithRules[FieldType[K, H] :: T, R] {
override def withRules(rules: R): Reads[FieldType[K, H] :: T] = new Reads[FieldType[K, H] :: T] {
override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
val name = witness.value
val rH = (__ \ name).read(readsH)
(rH and readsT.withRules(rules)) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T]).reads(json)
}
}
}
implicit def readRuleForHead[K <: Symbol, H, T <: HList, R <: HList](implicit
witness: Witness.Aux[K],
at: shapeless.ops.record.Selector.Aux[R, K, Reads[H]],
readsH: Reads[H],
readsT: ReadsWithRules[T, R]
): ReadsWithRules[FieldType[K, H] :: T, R] =
new ReadsWithRules[FieldType[K, H] :: T, R] {
override def withRules(rules: R): Reads[FieldType[K, H] :: T] = new Reads[FieldType[K, H] :: T] {
override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
val name = witness.value
val additionalRule: Reads[H] = at(rules)
val rH = (__ \ name).read(readsH) andKeep (__ \ name).read(additionalRule)
(rH and readsT.withRules(rules)) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T]).reads(json)
}
}
}
}
def readsWithRules[T, R <: HList](rules: R)(implicit readWithRule: ReadsWithRules[T, R]): Reads[T] =
readWithRule.withRules(rules)
case class MonPojo(numericField: Int)
val r: Reads[MonPojo] =
readsWithRules[MonPojo, FieldType[Symbol with Tagged["numericField"], Reads[Int]] :: HNil](
('numericField ->> (min(0) keepAnd max(150))) :: HNil
)
println(
r.reads(Json.obj(
"stringField" -> "Tata",
"numericField" -> 42
))
)
//JsSuccess(MonPojo(42),)
Then I want to build an HList of type Reads[V1] :: Reads[V2] ...
It's not clear why you need an HList of Reads rather than Reads of HList (so I guess you don't need another type class HReads, Reads should be enough). I guess you need to implement implicits:
implicit val readsHNil: Reads[HNil] = ???
implicit def readHCons[K <: Symbol, H, T <: HList](implicit
witness: Witness.Aux[K],
readsH: Reads[H],
readsT: Reads[T]): Reads[FieldType[K, H] :: T] = ???
implicit def readsGeneric[Repr, A](implicit
gen: LabelledGeneric.Aux[A, Repr],
readsRepr: Lazy[Reads[Repr]]): Reads[A] = ???
and similar two for coproducts if you need.
I wrote some implementations
import shapeless.{:+:, ::, CNil, Coproduct, HList, HNil, Inl, LabelledGeneric, Lazy, Witness}
import shapeless.labelled.FieldType
import play.api.libs.json._
import shapeless.syntax.singleton._
implicit val readsHNil: Reads[HNil] = Reads {
case JsArray(values) if values.isEmpty => JsSuccess(HNil)
case JsObject(values) if values.isEmpty => JsSuccess(HNil)
case _ => JsError()
}
private def listToJsResult[K <: Symbol, H, T <: HList](l: List[JsValue])(implicit
witness: Witness.Aux[K],
readsH: Reads[H],
readsT: Reads[T]): JsResult[FieldType[K, H] :: T] = {
val name = witness.value
l match {
case Nil => JsError()
case scala.::(head, tail) => for {
h <- readsH.reads(head)
t <- /*listToJsResult[K1, H1, T1](tail)*/ readsT.reads(JsArray(tail))
} yield (name ->> h).asInstanceOf[FieldType[K, H]] :: t
}
}
implicit val readsCNil: Reads[CNil] = Reads(_ => throw new Exception)
implicit def readHCons[K <: Symbol, H, T <: HList](implicit
witness: Witness.Aux[K],
readsH: Reads[H],
readsT: Reads[T]): Reads[FieldType[K, H] :: T] =
Reads {
case arr: JsArray => listToJsResult[K, H, T](arr.value.toList)
case obj: JsObject => listToJsResult[K, H, T](obj.values.toList)
case js => listToJsResult[K, H, T](List(js))
}
implicit def readCCons[K <: Symbol, H, T <: Coproduct](implicit
witness: Witness.Aux[K],
readsH: Reads[H],
readsT: Reads[T]): Reads[FieldType[K, H] :+: T] = {
val name = witness.value
Reads { json =>
(for {
h <- readsH.reads(json)
} yield Inl(name ->> h).asInstanceOf[FieldType[K, H] :+: T]) orElse {
for {
t <- readsT.reads(json)
} yield Inr(name ->> t).asInstanceOf[FieldType[K, H] :+: T]
}
}
}
implicit def readsGeneric[Repr, A](implicit
gen: LabelledGeneric.Aux[A, Repr],
readsRepr: Lazy[Reads[Repr]]): Reads[A] =
Reads(json => readsRepr.value.reads(json).map(gen.from))
def reads[A](json: JsValue)(implicit readsInst: Reads[A]): JsResult[A] = readsInst.reads(json)
but they seem to work incorrectly
and they seem to work correctly:
sealed trait MyTrait
case class MyClass1(x: Int, y: Int, z: Int) extends MyTrait
case class MyClass2(x: Int, y: Int) extends MyTrait
reads[MyClass1](JsObject(Seq("x" -> JsNumber(1), "y" -> JsNumber(2), "z" -> JsNumber(3))))
//JsSuccess(MyClass1(1,2,3),)
reads[MyTrait](JsObject(Seq("x" -> JsNumber(1), "y" -> JsNumber(2), "z" -> JsNumber(3))))
//JsSuccess(MyClass1(1,2,3),)
reads[MyTrait](JsObject(Seq("x" -> JsNumber(1), "y" -> JsNumber(2))))
//JsSuccess(MyClass2(1,2),)
The answer is partially based on library shapelaysson.
I can make your code work like this
implicit def readHCons[K <: Symbol, H, T <: HList, R <: HList](implicit
witness: Witness.Aux[K],
readsH: Reads[H],
readsT: Reads[T]): Reads[FieldType[K, H] :: T] =
new Reads[FieldType[K, H] :: T] {
override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
val name = witness.value
val jsonH = (__ \ name).read(readsH).reads(json)
val jsonT = readsT.reads(json)
(jsonH and jsonT) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T])
}
}
But my final goal is to be able to add additionnal rules: Something like
import validation2.ReadsWithRules._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
val r: Reads[MonPojo] = LabelledGeneric[MonPojo].readsWithRules(('numericField ->> (min(0) keepAnd max(150))) :: HNil)
r.reads(Json.obj(
"stringField" -> "Tata",
"numericField" -> 42
))
println(s"All Reads $r")
I tried to adapt your code with this
trait ReadsWithRules[T, R <: HList] {
def withRules(rules: R): Reads[T]
}
trait ReadsWithRulesLowerPriority {
implicit def readsHNil[R <: HNil]: ReadsWithRules[HNil, R] = new ReadsWithRules[HNil, R] {
def withRules(rules: R) =
new Reads[HNil] {
override def reads(json: JsValue): JsResult[HNil] = JsSuccess(HNil)
}
}
implicit def readHCons[K <: Symbol, H, T <: HList, R <: HList](implicit
witness: Witness.Aux[K],
readsH: Reads[H],
readsT: ReadsWithRules[T, R]): ReadsWithRules[FieldType[K, H] :: T, R] =
new ReadsWithRules[FieldType[K, H] :: T, R] {
override def withRules(rules: R) = new Reads[FieldType[K, H] :: T] {
override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
val name = witness.value
val jsonH = (__ \ name).read(readsH)
val jsonT = readsT.withRules(rules)
(jsonH and jsonT) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T]).reads(json)
}
}
}
}
object ReadsWithRules extends ReadsWithRulesLowerPriority {
implicit def readHConsWithRule[K <: Symbol, H, T <: HList, R <: HList](implicit
witness: Witness.Aux[K],
at: shapeless.ops.record.Selector.Aux[R, K, Reads[H]],
w: <:<[H, JsValue],
readsH: Reads[H],
readsT: ReadsWithRules[T, R]): ReadsWithRules[FieldType[K, H] :: T, R] =
new ReadsWithRules[FieldType[K, H] :: T, R] {
override def withRules(rules: R) = new Reads[FieldType[K, H] :: T] {
override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
val name = witness.value
val additionnalRule: Reads[H] = at(rules)
val jsonH = (__ \ name).read(readsH).andThen(additionnalRule)
val jsonT = readsT
(jsonH and jsonT.withRules(rules)) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T]).reads(json)
}
}
}
implicit def readsGeneric[Repr, A, R <: HList](implicit
gen: LabelledGeneric.Aux[A, Repr],
readsRepr: Lazy[ReadsWithRules[Repr, R]]): ReadsWithRules[A, R] =
new ReadsWithRules[A, R] {
override def withRules(rules: R) : Reads[A] = {
readsRepr.value.withRules(rules).map(r => gen.from(r))
}
}
implicit class WithRules[T](gen: LabelledGeneric[T]) {
def readsWithRules[R <: HList](rules: R)(implicit readWithRule: ReadsWithRules[T, R]): Reads[T] = {
readWithRule.withRules(rules)
}
}
}
But I've got an implicit resolution error.
My previous idea was to decompose the problems in sub step :
Step one T -> Reads[T1] :: Reads[T2] ...
Merge additionnal rules in Reads[T1] :: Reads[T2] ...
Sequence Reads[T1] :: Reads[T2] ... => Reads[T1 :: T2 ...]
But I fail at step 1 ...
I finally succeed another way :
object rules {
import play.api.libs.json._
import play.api.libs.functional.syntax._
import scala.annotation.implicitNotFound
import shapeless.labelled._
import shapeless.syntax.singleton._
import shapeless.{::, HList, HNil, LabelledGeneric, Lazy, Witness}
import shapeless.ops.record.Selector
trait SequenceReads[In <: HList] {
type Out
def apply(in: In): Reads[Out]
}
object SequenceReads {
import play.api.libs.functional.syntax._
type Aux[A <: HList, B <: HList] = SequenceReads[A] {type Out = B}
implicit def sequenceHnil[T, R <: HList, TR <: HList](): Aux[HNil, HNil] = new SequenceReads[HNil] {
type Out = HNil
override def apply(in: HNil): Reads[Out] = {
throw new RuntimeException("Oups")
}
}
implicit def sequenceSingleton[T, K <: Symbol](
implicit witness: Witness.Aux[K]
): Aux[FieldType[K, Reads[T]] :: HNil, T :: HNil] = new SequenceReads[FieldType[K, Reads[T]] :: HNil] {
type Out = T :: HNil
override def apply(in: FieldType[K, Reads[T]] :: HNil): Reads[T :: HNil] = {
val name = witness.value.name
(__ \ name).read(in.head).map(_ :: HNil)
}
}
implicit def sequence[T, K <: Symbol, R <: HList, TR <: HList](
implicit
witness: Witness.Aux[K],
req: Lazy[SequenceReads.Aux[R, TR]]
): Aux[FieldType[K, Reads[T]] :: R, T :: TR] = new SequenceReads[FieldType[K, Reads[T]] :: R] {
type Out = T :: TR
override def apply(in: FieldType[K, Reads[T]] :: R): Reads[Out] = {
val name = witness.value.name
val head: Reads[T] = (__ \ name).read(in.head)
val value: Reads[TR] = req.value.apply(in.tail)
(head and value) {
_ :: _
}
}
}
implicit class SequenceReadsOps[In <: HList](in: In) {
class Builder[Out <: HList] {
def apply(
implicit
sequence: SequenceReads.Aux[In, Out]
): Reads[Out] = {
sequence(in)
}
}
def sequence[R <: HList](implicit s: SequenceReads.Aux[In, R]) = new Builder[R].apply(s)
}
}
#implicitNotFound("Implicit not found: Rules type or fields are not valid")
trait RuleValidation[Repr <: HList, Rules <: HList]
object RuleValidation {
implicit def validateHNil[Repr <: HList] : RuleValidation[Repr, HNil] =
new RuleValidation[Repr, HNil] {}
implicit def validateSingleton[Repr <: HList, K <: Symbol, V] (
implicit
sel: Selector.Aux[Repr, K, V]
): RuleValidation[Repr, FieldType[K, Reads[V]] :: HNil] =
new RuleValidation[Repr, FieldType[K, Reads[V]] :: HNil] {}
implicit def validateHCons[Repr <: HList, H, R <: HList, K <: Symbol, V] (
implicit
sel: Selector.Aux[Repr, K, V],
validation: RuleValidation[Repr, R]
): RuleValidation[Repr, FieldType[K, Reads[V]] :: R] =
new RuleValidation[Repr, FieldType[K, Reads[V]] :: R] {}
}
object ReadsWithRules {
implicit def readsHNil: Reads[HNil] = new Reads[HNil] {
override def reads(json: JsValue): JsResult[HNil] = JsSuccess(HNil)
}
implicit def readHCons[K <: Symbol, H, T <: HList, R <: HList](implicit
witness: Witness.Aux[K],
readsH: Reads[H],
readsT: Reads[T]): Reads[FieldType[K, H] :: T] =
new Reads[FieldType[K, H] :: T] {
override def reads(json: JsValue): JsResult[FieldType[K, H] :: T] = {
val name = witness.value
val jsonH = (__ \ name).read(readsH)
val jsonT = readsT
(jsonH and jsonT) ((a, b) => (name ->> a :: b).asInstanceOf[FieldType[K, H] :: T]).reads(json)
}
}
implicit def readsGeneric[Repr, A, R <: HList](implicit
gen: LabelledGeneric.Aux[A, Repr],
readsRepr: Lazy[Reads[Repr]]): Reads[A] = {
readsRepr.value.map(r => gen.from(r))
}
}
trait JsonRead[T]
def jsonRead[T]: JsonRead[T] = new JsonRead[T] {}
implicit class WithRules[A](gen: JsonRead[A]) {
def readsWithRules[R <: HList, K <: Symbol, V, T0 <: HList, ARepr <: HList, AKeys <: HList, RKeys <: HList, Validation <: HList](rules: FieldType[K, V] :: T0)(
implicit
genA: LabelledGeneric.Aux[A, ARepr],
readsInst: Reads[A],
sequenceReads: SequenceReads[FieldType[K, V] :: T0],
validation: RuleValidation[ARepr, FieldType[K, V] :: T0]
): Reads[A] = Reads[A] { json =>
val valueA: JsResult[A] = readsInst.reads(json)
val valueR: JsResult[sequenceReads.Out] = sequenceReads(rules).reads(json)
(valueA, valueR) match {
case (err1: JsError, err2: JsError) => err1 ++ err2
case (err1: JsError, JsSuccess(_, _)) => err1
case (JsSuccess(_, _), err2: JsError) => err2
case (JsSuccess(v, p), _) => JsSuccess(v, p)
}
}
}
}
And the test
object Test extends App {
import play.api.libs.json._
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import shapeless._
import syntax.singleton._
import rules._
case class Other(name: String)
case class MonPojo(toto: String, tata: Int, other: Other)
object MonPojo {
implicit val readsOther = Json.reads[Other]
implicit val reads: Reads[MonPojo] = Json.reads[MonPojo]
}
val strReads = pattern(".*".r)
private val value: Reads[MonPojo] = jsonRead[MonPojo].readsWithRules(
('tata ->> (min(0) keepAnd max(150))) ::
HNil
)
println(s"!!! ${
value.reads(Json.obj(
"toto" -> "test",
"other" -> Json.obj("name" -> "test"),
"tata" -> 25
))
}")
}
Thanks for your help, I will try your solution.
In this question, I noticed the need to separate logic into two steps with two class constructors and yet to fully understand the rationale behind it. But here I am, having a more complex problem.
In this case I need to achieve the following
case class RawReq(ip: String, header: Map[String, String] = Map())
case class Result(status: String, body: Option[String] = None)
type Directive[T] = T ⇒ Result
class ActionConstructor[T] {
def apply[ExtractedRepr <: HList, ExtraInputRepr <: HList]
(extractor: RawReq ⇒ ExtractedRepr, dir: Directive[T])
(implicit supplement: Supplement.Aux[T, ExtractedRepr, ExtraInputRepr])
: ExtraInputRepr => RawReq => Result
= ???
}
// The Supplement is something to be implemented or replaced ( it should
// generate a ExtraInputRepr type that basically provide the missing
// fields that ExtractedRepr doesn't provide for creating a T, the
// example below explains it better.
// Example usage below
case class RequestMessage(name: String, ipAllowed: Boolean, userId: String)
val dir : Directive[RequestMessage] = (rm: RequestMessage) ⇒
if(rm.ipAllowed)
Result("200", Some("hello! " + rm.name ))
else Result("401")
// the extractor can get one piece of information from the RawReq
val extractor = (r: RawReq) => ('ipAllowed ->> (r.ip.startWith("10.4")> 3)) :: HNil
val ac = new ActionConstructor[RequestMessage]
val action = ac(extractor, dir)
// The other two fields of RequestMessage are provided throw method call
val result = action(('name ->> "Mike") :: ('userId ->> "aId") :: HNil)(RawReq(ip = "10.4.2.5"))
result == Result("200", Some("hello! Mike"))
My several attempts to implement this all failed. Here is the last one
class PartialHandlerConstructor[T, Repr <: HList, ExtractedRepr <: HList]
(extractor: RawReq ⇒ ExtractedRepr)
(implicit lgen: LabelledGeneric.Aux[T, Repr]) {
def apply[TempFull <: HList, InputRepr <: HList]
(dir: Directive[T])
(implicit removeAll: RemoveAll.Aux[Repr, ExtractedRepr, InputRepr],
prepend: Prepend.Aux[InputRepr, ExtractedRepr, TempFull],
align: Align[TempFull, Repr]): InputRepr ⇒ RawReq ⇒ Result =
(inputRepr: InputRepr) ⇒ (raw: RawReq) ⇒ {
dir(lgen.from(align(inputRepr ++ extractor(raw))))
}
}
class HandlerConstructor[T]() {
def apply[ExtractedRepr <: HList, Repr <: HList]
(extractor: RawReq ⇒ ExtractedRepr)
(implicit lgen: LabelledGeneric.Aux[T, Repr]) = {
new PartialHandlerConstructor[T, Repr, ExtractedRepr](extractor)
}
}
val hc = new HandlerConstructor[RequestMessage]
val handler1 = hc((r: RawReq) ⇒ ('ipAllowed ->> (r.ip.length > 3)) :: HNil)
val dir: Directive[RequestMessage] = (rm: RequestMessage) ⇒ Result(rm.ipAllowed.toString, rm.name)
val action = handler1(dir) // <=== compiler fail
val result = action(('name ->> "big") :: ('userId ->> "aId") :: HNil)(RawReq("anewiP"))
The compiler error message I am getting is at the line p(dir)
Error:(85, 26) could not find implicit value for parameter removeAll:
shapeless.ops.hlist.RemoveAll.Aux[this.Out,shapeless.::[Boolean with
shapeless.labelled.KeyTag[Symbol with
shapeless.tag.Tagged[String("ipAllowed")],Boolean],shapeless.HNil],InputRepr]
val action = handler1(dir)
^
I think the fundamental reason that I failed is that I don't understand why and how to organize this type construction logic into different classes. And I don't quite understand why the compiler can find the implicit in some cases but not in others.
Figured out a solution. I have to create my own TypeClass RestOf
package com.iheart.playSwagger
import org.specs2.mutable.Specification
import shapeless._
import ops.hlist._
import syntax.singleton._
object Test {
case class RawReq(ip: String, header: Map[String, String] = Map())
case class Result(status: String, body: String)
type Directive[T] = T ⇒ Result
case class RequestMessage(name: String, ipAllowed: Boolean, userId: String)
trait RestOf[L <: HList, SL <: HList] {
type Out <: HList
}
object RestOf {
type Aux[L <: HList, SL <: HList, Out0 <: HList] = RestOf[L, SL] {
type Out = Out0
}
implicit def hlistRestOfNil[L <: HList]: Aux[L, HNil, L] = new RestOf[L, HNil] { type Out = L }
implicit def hlistRestOf[L <: HList, E, RemE <: HList, Rem <: HList, SLT <: HList]
(implicit rt: Remove.Aux[L, E, (E, RemE)], st: Aux[RemE, SLT, Rem]): Aux[L, E :: SLT, Rem] =
new RestOf[L, E :: SLT] { type Out = Rem }
}
class PartialHandlerConstructor[T, Repr <: HList, ExtractedRepr <: HList, InputRepr <: HList]
(extractor: RawReq ⇒ ExtractedRepr)
(implicit lgen: LabelledGeneric.Aux[T, Repr]) {
def apply[TempFull <: HList](dir: Directive[T])
(implicit prepend: Prepend.Aux[InputRepr, ExtractedRepr, TempFull],
align: Align[TempFull, Repr]): InputRepr ⇒ RawReq ⇒ Result =
(inputRepr: InputRepr) ⇒ (raw: RawReq) ⇒ {
dir(lgen.from(align(inputRepr ++ extractor(raw))))
}
}
class HandlerConstructor[T]() {
def apply[Repr <: HList,ExtractedRepr <: HList, InputRepr <: HList]
(extractor: RawReq ⇒ ExtractedRepr)
(implicit lgen: LabelledGeneric.Aux[T, Repr],
restOf: RestOf.Aux[Repr, ExtractedRepr, InputRepr]) = {
new PartialHandlerConstructor[T, Repr, ExtractedRepr, InputRepr](extractor)
}
}
}
class HandlerSpec extends Specification {
import Test._
"handler" should {
"generate action functions" in {
val hc = new HandlerConstructor[RequestMessage]().apply
val handler1 = hc((r: RawReq) ⇒ ('ipAllowed ->> (r.ip.length > 3)) :: HNil)
val dir: Directive[RequestMessage] =
(rm: RequestMessage) ⇒ Result(rm.ipAllowed.toString, rm.name)
val action = handler1(dir)
val result = action(('name ->> "big") :: ('userId ->> "aId") :: HNil)(RawReq("anewiP"))
result === Result("true", "big")
}
}
}