Expressing Types as values using GADTS in scala and various other techniques. Using Schemas in this style allows subsequent derivation ot Typeclass instances (and other fun things)
If you want to play around with a minimal example of the involved code here you go: https://scalafiddle.io/sf/YSrSwQy/1
15. sealed trait Employee
final case class Subordinate(
name:String,
age:Int,
boss: Employee
) extends Employee
final case class Boss(
name:String,
isActive:Boolean
) extends Employee
19. sealed trait MyPrim[A]
final case object IntP extends MyPrim[Int]
final case object StringP extends MyPrim[String]
final case object BooleanP extends MyPrim[Boolean]
25. val intSchema:SchemaF[MyPrim, Int] = PrimS(IntP)
val strSchema:SchemaF[MyPrim, String] = PrimS(StringP)
val booleanSchema:SchemaF[MyPrim, Boolean] = PrimS(BooleanP)
26. val nt: MyPrim ~> Encoder = new NaturalTransformation[MyPrim, Encoder]{
override def apply[A](prim:MyPrim[A]):Encoder[A] = toPrimEnc(prim)
}
val encoder: Encoder[Int] = toEnc(intSchema, nt)
27.
28. sealed trait SchemaF[Prim[_], A]
final case class PrimS[Prim[_],A](prim:Prim[A]) extends SchemaF[Prim, A]
final case class ProdS[Prim[_],A,B](l:SchemaF[Prim, A], r:SchemaF[Prim, B]) extends SchemaF[Prim, (A,B)]
final case class SumS[Prim[_],A,B](l:SchemaF[Prim, A], r:SchemaF[Prim, B]) extends SchemaF[Prim, A/B]
31. def toEnc[Prim[_], A](schema:SchemaF[Prim, A], toPrimEnc: Prim ~> Encoder):Encoder[A] = schema match {
case PrimS(p) => toPrimEnc(p)
case prod:ProdS[Prim, x, y] => (tpl:(x,y)) =>
toEnc(prod.l, toPrimEnc)(tpl._1) + "," + toEnc(prod.r, toPrimEnc)(tpl._2)
}
32. def toEnc[Prim[_], A](schema:SchemaF[Prim, A], toPrimEnc: Prim ~> Encoder):Encoder[A] = schema match {
case PrimS(p) => toPrimEnc(p)
case prod:ProdS[Prim, x, y] => (tpl:(x,y)) =>
toEnc(prod.l, toPrimEnc)(tpl._1) + "," + toEnc(prod.r, toPrimEnc)(tpl._2)
case sum:SumS[Prim, x, y] => (e:x / y) => e.fold(
lx => toEnc(sum.l, toPrimEnc)(lx),
ry => toEnc(sum.r, toPrimEnc)(ry)
)
}
33.
34.
35. sealed trait SchemaF[Prim[_], A]
final case class PrimS[Prim[_],A](prim:Prim[A]) extends SchemaF[Prim, A]
final case class ProdS[Prim[_],A,B](l:SchemaF[Prim, A], r:SchemaF[Prim, B]) extends SchemaF[Prim, (A,B)]
final case class SumS[Prim[_],A,B](l:SchemaF[Prim, A], r:SchemaF[Prim, B]) extends SchemaF[Prim, A/B]
final case class UnionS[Prim[_],B,A](base:SchemaF[Prim, B], iso:Iso[B,A]) extends SchemaF[Prim, A]
final case class RecordS[Prim[_],B,A](base:SchemaF[Prim, B], iso:Iso[B,A]) extends SchemaF[Prim, A]
final case class IsoS[Prim[_],B,A](base:SchemaF[Prim, B], iso:Iso[B,A]) extends SchemaF[Prim, A]
36.
37. def toEnc[Prim[_], A](schema:SchemaF[Prim, A], toPrimEnc: Prim ~> Encoder):Encoder[A] = schema match {
case PrimS(p) => toPrimEnc(p)
case prod:ProdS[Prim, x, y] => (tpl:(x,y)) =>
toEnc(prod.l, toPrimEnc)(tpl._1) + "," + toEnc(prod.r, toPrimEnc)(tpl._2)
case sum:SumS[Prim, x, y] => (e:x / y) => e.fold(
lx => toEnc(sum.l, toPrimEnc)(lx),
ry => toEnc(sum.r, toPrimEnc)(ry)
)
case isoS:IsoS[Prim, b, A] => (a:A) =>
toEnc(isoS.base, toPrimEnc)(isoS.iso.reverseGet(a))
case union:UnionS[Prim, b, A] => (a:A) =>
toEnc(union.base, toPrimEnc)(union.iso.reverseGet(a))
case record:RecordS[Prim, b, A] => (a:A) =>
toEnc(record.base, toPrimEnc)(record.iso.reverseGet(a))
}
38.
39.
40. sealed trait SchemaF[Prim[_], SumTermId, ProductTermId, A]
...
final case class FieldS[Prim[_], SumTermId, ProductTermId, A](
id:ProductTermId,
base:SchemaF[Prim,SumTermId, ProductTermId, A]
) extends SchemaF[Prim, SumTermId, ProductTermId, A]
final case class BranchS[Prim[_], SumTermId, ProductTermId, A](
id:SumTermId,
base:SchemaF[Prim,SumTermId, ProductTermId, A]
) extends SchemaF[Prim, SumTermId, ProductTermId, A]
59. trait SchemaModule[R <: Realisation] {
val R: R
type Schema[A] = SchemaF[R.Prim, R.SumTermId, R.ProductTermId, A]
type Sum[A, B] = SumS[R.Prim, R.SumTermId, R.ProductTermId, A, B]
// and all the other needed aliases
}
60. trait SchemaModule[R <: Realisation] {
val R: R
final def unit: Schema[Unit]
final def prim[A](prim: R.Prim[A]): Schema[A]
final def union[A, AE](choices: Schema[AE], iso: Iso[AE, A]): Schema[A]
final def optional[A](aSchema: Schema[A]): Schema[Option[A]]
final def record[A, An](terms: Schema[An], isoA: Iso[An, A]): Schema[A]
final def seq[A](element: Schema[A]): Schema[List[A]]
final def iso[A0, A](base: Schema[A0],iso: Iso[A0, A]): Schema[A]
final def self[A](root: => Schema[A]): Schema[A]
}
62. object JsonSchema extends Realisation {
type Prim[A] = JsonPrim[A]
type ProductTermId = String
type SumTermId = String
sealed trait JsonPrim[A]
final case object JsonString extends JsonPrim[String]
final case object JsonNumber extends JsonPrim[BigDecimal]
final case object JsonBool extends JsonPrim[Boolean]
final case object JsonNull extends JsonPrim[Unit]
}
65. sealed trait Employee
final case class Subordinate(
name:String,
age:Int,
boss: Employee
) extends Employee
final case class Boss(
name:String,
isActive:Boolean
) extends Employee
70. object Representation {
type RSum[RA, A, RB, B]
type RProd[RA, A, RB, B]
type RIso[RA, A, B]
type RSelf[A]
type RSeq[R, A]
type -*>[K, V]
type -+>[K, V]
type RRecord[RA, An, A]
type RUnion[RA, An, A]
}
71. sealed trait SchemaF[Prim[_], SumTermId, ProductTermId, R, A]
final case class One[...]() extends SchemaF[... Unit, Unit]
final case class SumF[...](...) extends SchemaF[... RSum[RA, A, RB, B], A / B]
final case class ProdF[...](...) extends SchemaF[... RProd[RA, A, RB, B], (A, B)]
final case class BranchF[...](...) extends SchemaF[... -+>[SumTermId, RA], A]
final case class UnionF[... RA: IsUnion ...](...) extends SchemaF[... RUnion[RA, AE, A], A]
final case class FieldF[...](...) extends SchemaF[... -*>[ProductTermId, RA], A]
final case class RecordF[...RA: IsRecord...](...) extends SchemaF[... RRecord[RA, AP, A], A]
final case class SeqF[...](...) extends SchemaF[... RSeq[RA, A], List[A]]
final case class IsoSchemaF[...](...) extends SchemaF[... RIso[RA, A0, A], A]
final case class SelfReference[...](...) extends SchemaF[... RSelf[A], A]
72. final case class Boss(name:String, isActive:Boolean) extends Employee
def bossSchema = record(
"name" -*>: prim(JsonSchema.JsonString) :*:
"isActive" -*>: prim(JsonSchema.JsonBool),
Iso[(String, Boolean), Boss]((Boss.apply _).tupled)(b => (b.name, b.isActive))
)
74. sealed trait SchemaF[Prim[_], SumTermId, ProductTermId, R, A]
final case class One[...]() extends SchemaF[... Unit, Unit]
final case class SumF[...](...) extends SchemaF[... RSum[RA, A, RB, B], A / B]
final case class ProdF[...](...) extends SchemaF[... RProd[RA, A, RB, B], (A, B)]
final case class BranchF[...](...) extends SchemaF[... -+>[SumTermId, RA], A]
final case class UnionF[... RA: IsUnion ...](...) extends SchemaF[... RUnion[RA, AE, A], A]
final case class FieldF[...](...) extends SchemaF[... -*>[ProductTermId, RA], A]
final case class RecordF[...RA: IsRecord...](...) extends SchemaF[... RRecord[RA, AP, A], A]
final case class SeqF[...](...) extends SchemaF[... RSeq[RA, A], List[A]]
final case class IsoSchemaF[...](...) extends SchemaF[... RIso[RA, A0, A], A]
final case class SelfReference[...](...) extends SchemaF[... RSelf[A], A]
75. @implicitNotFound(
msg = "It seems like the following representation type isn't isomorphic to a product of named
fields: ${A}"
)
trait IsRecord[A]
object IsRecord {
implicit def singleFieldIsRecord[K, V]: IsRecord[K -*> V] = new IsRecord[K -*> V] {}
implicit def productIsRecord[L: IsRecord, R: IsRecord, X, Y]: IsRecord[RProd[L, X, R, Y]] =
new IsRecord[RProd[L, X, R, Y]] {}
implicit def isoIsRecord[R: IsRecord, A0, A]: IsRecord[RIso[R, A0, A]] =
new IsRecord[RIso[R, A0, A]] {}
}