Created
March 10, 2016 05:47
-
-
Save shajra/bd84c3d40002e008f92f to your computer and use it in GitHub Desktop.
Example of using a tagless encoding (rough draft)
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 shajra.tagless | |
import argonaut.{ HCursor, DecodeResult, DecodeJson, Parse } | |
import argonaut.Argonaut.jString | |
import scalaz._ | |
import scalaz.Isomorphism.<~> | |
import scalaz.syntax.monad._ | |
case class Field(name: FieldName, desc: FieldDesc) | |
case class FieldName(value: String) extends AnyVal | |
case class FieldDesc(value: String) extends AnyVal | |
case class IsPrimitive[A, E[_]](interp: E[A]) | |
trait Rules[E[_]] { | |
def required[A](field: Field)(implicit ev: IsPrimitive[A, E]): E[A] | |
def optional[A](field: Field)(implicit ev: IsPrimitive[A, E]): E[Maybe[A]] | |
} | |
object Rules { | |
@inline def apply[E[_]](implicit r: Rules[E]): Rules[E] = r | |
} | |
case class Render[A](terms: IList[String]) | |
object Render { | |
implicit val renderApply: Apply[Render] = | |
new Apply[Render] { | |
def ap[A, B](fa: => Render[A])(f: => Render[(A) => B]) = | |
Render[B](f.terms ++ fa.terms) | |
def map[A, B](fa: Render[A])(f: (A) => B) = | |
Render[B](fa.terms) | |
} | |
implicit val isPrimBool: IsPrimitive[Boolean, Render] = | |
IsPrimitive[Boolean, Render](Render[Boolean](IList("boolean"))) | |
implicit val isPrimInt: IsPrimitive[Int, Render] = | |
IsPrimitive[Int, Render](Render[Int](IList("integer"))) | |
implicit val rules: Rules[Render] = | |
// TODO: refactor copy/paste away | |
new Rules[Render] { | |
def required[A](field: Field)(implicit ev: IsPrimitive[A, Render]) = | |
Render[A](ev.interp.terms map { p => | |
s"required: ${field.name.value}=${p} (${field.desc.value}})" | |
}) | |
def optional[A](field: Field)(implicit ev: IsPrimitive[A, Render]) = | |
Render[Maybe[A]](ev.interp.terms map { p => | |
s"optional: ${field.name.value}=${p} (${field.desc.value}})" | |
}) | |
} | |
} | |
trait DecodeJsonImplicits { | |
type JsonReader[A] = Kleisli[DecodeResult, HCursor, A] | |
implicit val monad: Monad[DecodeJson] = | |
new IsomorphismMonad[DecodeJson, JsonReader] { | |
def G: Monad[JsonReader] = Monad[JsonReader] | |
def iso: DecodeJson <~> JsonReader = | |
new (DecodeJson <~> JsonReader) { | |
override def to: DecodeJson ~> JsonReader = | |
new (DecodeJson ~> JsonReader) { | |
override def apply[A](dj: DecodeJson[A]) = dj.kleisli | |
} | |
override def from: JsonReader ~> DecodeJson = | |
new (JsonReader ~> DecodeJson) { | |
override def apply[A](jr: JsonReader[A]) = DecodeJson(jr.run) | |
} | |
} | |
} | |
implicit def isPrim[A : DecodeJson]: IsPrimitive[A, DecodeJson] = | |
IsPrimitive[A, DecodeJson](implicitly[DecodeJson[A]]) | |
implicit val rules: Rules[DecodeJson] = | |
// TODO: get Decode instances from IsPrimitive implicitly | |
new Rules[DecodeJson] { | |
def required[A] | |
(field: Field)(implicit ev: IsPrimitive[A, DecodeJson]) | |
: DecodeJson[A] = | |
DecodeJson { _ --\ field.name.value as ev.interp } | |
def optional[A] | |
(field: Field)(implicit ev: IsPrimitive[A, DecodeJson]) | |
: DecodeJson[Maybe[A]] = | |
DecodeJson | |
.OptionDecodeJson(DecodeJson { _ --\ field.name.value as ev.interp }) | |
.map(Maybe.fromOption) | |
} | |
} | |
object Play extends App with DecodeJsonImplicits { | |
val knowsCrazy = Field(FieldName("knowsCrazy"), FieldDesc("don't know karate")) | |
val numPets = Field(FieldName("numPets"), FieldDesc("number of pets owned")) | |
def rule[E[_]: Rules : Apply] | |
(implicit boolIsPrim: IsPrimitive[Boolean, E], | |
intIsPrim: IsPrimitive[Int, E]) | |
: E[(Boolean, Maybe[Int])] = { | |
val interp = Rules[E] | |
import interp._ | |
(required[Boolean](knowsCrazy) |@| optional[Int](numPets)) { (b, i) => (b, i) } | |
} | |
println(rule[Render].terms) | |
println(Parse.decode("""{"knowsCrazy": true, "numPets": false}""")(rule[DecodeJson])) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment