Skip to content

Instantly share code, notes, and snippets.

@Fristi
Last active August 31, 2016 07:38
Show Gist options
  • Save Fristi/1c345f561294e36262be339f96379508 to your computer and use it in GitHub Desktop.
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
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