Last active
August 31, 2016 07:38
-
-
Save Fristi/1c345f561294e36262be339f96379508 to your computer and use it in GitHub Desktop.
Introspectable Json algebra. Allows you to derive codecs and documentation. Category + Monoid lifted into the algebra GADT. Composing algebra's is possible via the heterogenous polymorphic tail. Port of JsonGrammar2 by Martijn van Steenbergen en Sjoerd Visscher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package net.vectos | |
import cats.arrow.Category | |
import io.circe.Json | |
import shapeless._ | |
import scala.language.higherKinds | |
object algebra { | |
sealed trait JsonGrammarF[F[-_,+_]] extends Category[F] { | |
//TODO: ArrowPlus | |
def empty[T]: F[T, T] | |
def or[A, B](x: F[A, B], y: F[A, B]): F[A, B] | |
def pure[T1, T2](f: T1 => Option[T2], g: T2 => Option[T1]): F[T1, T2] | |
def obj[T1 <: HList, T2 <: HList](props: F[T1, T2]): F[T1, T2] | |
def string[T <: HList](name: String): F[String :: T, T] | |
def int[T <: HList](name: String): F[Int :: T, T] | |
def double[T <: HList](name: String): F[Double :: T, T] | |
def prop[A, T <: HList](name: String, grammar: F[A, T]): F[A :: T, T] | |
def stack[A, B, T <: HList](grammar: F[A, B]): F[A :: T, B :: T] | |
def unstack[A, B <: HList](grammar: F[A :: HNil, B]): F[A, B] | |
} | |
} | |
object dsl { | |
import algebra._ | |
sealed trait Dsl[-A, +B] { | |
def apply[F[-_,+_]: JsonGrammarF]: F[A, B] | |
} | |
def id[T]: Dsl[T, T] = new Dsl[T, T] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].id[T] | |
} | |
def combine[A, B, C](x: Dsl[A, B], y: Dsl[B, C]): Dsl[A, C] = new Dsl[A, C] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].compose(y.apply[F], x.apply[F]) | |
} | |
def empty[T]: Dsl[T, T] = new Dsl[T, T] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].empty[T] | |
} | |
def pure[T1, T2](f: T1 => Option[T2], g: T2 => Option[T1]): Dsl[T1, T2] = new Dsl[T1, T2] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].pure(f,g) | |
} | |
def or[A, B](x: Dsl[A, B], y: Dsl[A, B]): Dsl[A, B] = new Dsl[A, B] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].or[A, B](x.apply[F], y.apply[F]) | |
} | |
def obj[T1 <: HList, T2 <: HList](props: Dsl[T1, T2]): Dsl[T1, T2] = new Dsl[T1, T2] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].obj(props.apply[F]) | |
} | |
def string[T <: HList](name: String): Dsl[String :: T, T] = new Dsl[String :: T, T] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].string(name) | |
} | |
def int[T <: HList](name: String): Dsl[Int :: T, T] = new Dsl[Int :: T, T] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].int(name) | |
} | |
def double[T <: HList](name: String): Dsl[Double :: T, T] = new Dsl[Double :: T, T] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].double(name) | |
} | |
def prop[A, T <: HList](name: String, grammar: Dsl[A, T]): Dsl[A :: T, T] = new Dsl[A :: T, T] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].prop(name, grammar.apply[F]) | |
} | |
def stack[A, B, T <: HList](grammar: Dsl[A, B]) = new Dsl[A :: T, B :: T] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].stack(grammar.apply[F]) | |
} | |
def unstack[A, B <: HList](grammar: Dsl[A :: HNil, B]) = new Dsl[A, B] { | |
def apply[F[-_,+_] : JsonGrammarF] = implicitly[JsonGrammarF[F]].unstack(grammar.apply[F]) | |
} | |
def generic[T](implicit G: Generic[T]): Dsl[T :: HNil, G.Repr] = | |
pure((z: T :: HNil) => Some(G.to(z.head)), (z: G.Repr) => Some(G.from(z) :: HNil)) | |
implicit val category = new Category[Dsl] { | |
override def id[A]: Dsl[A, A] = id[A] | |
override def compose[A, B, C](f: Dsl[B, C], g: Dsl[A, B]): Dsl[A, C] = combine(g, f) | |
} | |
} | |
object encoder { | |
import algebra._ | |
import dsl._ | |
private def insertIntoObj(obj: Json, name: String, insert: Json) = obj deepMerge Json.fromFields(List(name -> insert)) | |
private trait Encoder[-T1, +T2] { self => | |
def apply(a: T1, current: Json): Option[(T2, Json)] | |
def andThen[T3](other: Encoder[T2, T3]) = Encoder[T1, T3] { case (a, ja) => | |
for { | |
(b, jb) <- self.apply(a, ja) | |
(c, jc) <- other.apply(b, jb) | |
} yield c -> jc | |
} | |
} | |
private object Encoder { | |
def apply[T1, T2](f: (T1, Json) => Option[(T2, Json)]) = new Encoder[T1, T2] { | |
override def apply(a: T1, current: Json): Option[(T2, Json)] = f(a, current) | |
} | |
def stack[T1, T2, T <: HList](e: Encoder[T1, T2]) = | |
Encoder[T1 :: T, T2 :: T] { case (a :: tail, ja) => e.apply(a, ja).map { case (b, jb) => (b :: tail) -> jb} } | |
def unstack[T1, T2 <: HList](e: Encoder[T1 :: HNil, T2]) = | |
Encoder[T1, T2] { case (a, ja) => e.apply(a :: HNil, ja).map { case (b, jb) => b -> jb }} | |
def pure[T1, T2](f: T1 => Option[T2]) = Encoder[T1, T2]((a, json) => f(a).map(b => b -> json)) | |
def id[T] = Encoder[T, T]((t,json) => Some(t -> json)) | |
def empty[T] = Encoder[T, T]((_, _) => None) | |
def choose[T1, T2](x: Encoder[T1, T2], y: Encoder[T1, T2]) = Encoder[T1, T2] { case (a, ja) => x.apply(a, ja) orElse y.apply(a, ja) } | |
} | |
def encode[X, Y](dsl: Dsl[X, Y])(x: X) = { | |
val encoder = dsl.apply(new JsonGrammarF[Encoder] { | |
override def empty[T]: Encoder[T, T] = Encoder((_,_) => None) | |
override def or[A, B](x: Encoder[A, B], y: Encoder[A, B]): Encoder[A, B] = Encoder.choose(x, y) | |
override def int[T <: HList](name: String): Encoder[::[Int, T], T] = | |
Encoder[Int :: T, T] { case (a, json) => Some(a.tail -> insertIntoObj(json, name, Json.fromInt(a.head))) } | |
override def prop[A, T <: HList](name: String, grammar: Encoder[A, T]): Encoder[::[A, T], T] = | |
Encoder[A :: T, T] { case (a, json) => grammar(a.head, Json.Null).map { case (_, tail) => a.tail -> insertIntoObj(json, name, tail) } } | |
override def string[T <: HList](name: String): Encoder[::[String, T], T] = | |
Encoder[String :: T, T] { case (a, json) => Some(a.tail -> insertIntoObj(json, name, Json.fromString(a.head))) } | |
override def double[T <: HList](name: String): Encoder[::[Double, T], T] = | |
Encoder[Double :: T, T] { case (a, json) => Json.fromDouble(a.head).map(j => a.tail -> insertIntoObj(json, name, j)) } | |
override def stack[A, B, T <: HList](grammar: Encoder[A, B]): Encoder[::[A, T], ::[B, T]] = Encoder.stack(grammar) | |
override def unstack[A, B <: HList](grammar: Encoder[::[A, HNil], B]): Encoder[A, B] = Encoder.unstack(grammar) | |
override def pure[T1, T2](f: (T1) => Option[T2], g: (T2) => Option[T1]): Encoder[T1, T2] = Encoder.pure(f) | |
override def obj[T1 <: HList, T2 <: HList](props: Encoder[T1, T2]): Encoder[T1, T2] = props | |
override def id[A]: Encoder[A, A] = Encoder.id | |
override def compose[A, B, C](f: Encoder[B, C], g: Encoder[A, B]): Encoder[A, C] = g andThen f | |
}) | |
encoder(x, Json.Null) | |
} | |
} | |
case class Address(street: String, houseNumber: Int, uselessNumber: Double) | |
case class Employee(name: String, address: Address) | |
case class Location(x: Int, y: Int) | |
object JsonGrammarApp extends App { | |
import cats.syntax.all._ | |
import dsl._ | |
def loc[T <: HList] = | |
generic[Location] andThen obj(int("x") andThen int("y")) | |
def address[T <: HList] = | |
generic[Address] andThen obj(string("street").andThen(int("houseNumber").andThen(double("uselessNumber")))) | |
val employee = | |
unstack(generic[Employee] andThen obj(string("name") andThen prop("address", unstack(address)))) | |
println(encoder.encode(employee)(Employee("Mark", Address("street", 23, 33.0d)))) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment