Skip to content

Instantly share code, notes, and snippets.

@fsarradin
Created March 29, 2019 11:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fsarradin/178876f079f29aa1092a9326899043ef to your computer and use it in GitHub Desktop.
Save fsarradin/178876f079f29aa1092a9326899043ef to your computer and use it in GitHub Desktop.
Typeclass derivation in Dotty
import scala.reflect._
import scala.compiletime._
import scala.compiletime.Shape._
enum Maybe[+A] derives ToJson {
case Just(value: A)
case Nope
}
case class Person(id: String, name: String, age: Int) derives ToJson
object DeriveMain {
import ToJson._
// alternative declarations:
// implied [A: ToJson] for ToJson[Maybe[A]] = ToJson.derived
// implied for ToJson[Person] = ToJson.derived
def main(args: Array[String]): Unit = {
val a: Maybe[Person] = Maybe.Just(Person("1", "john", 32))
println(a.toJson)
}
}
trait ToJson[A] {
def (value: A) toJson: String
}
object ToJson {
inline def derived[A] given (ev: Generic[A]): ToJson[A] =
new ToJson[A] {
def (value: A) toJson: String = {
val mvalue = ev.reflect(value)
inline erasedValue[ev.Shape] match {
case t: Cases[alts] =>
toJsonCases[alts](mvalue)
case _: Case[_, elems] =>
"{ " + toJsonElems[elems](mvalue) + " }"
}
}
}
inline def toJsonCases[Alts <: Tuple](mvalue: Mirror, n: Int = 0): String =
inline erasedValue[Alts] match {
case _: (Case[_, elems] *: tail) =>
if (mvalue.ordinal == n)
"{ " + toJsonElems[elems](mvalue) + " }"
else
toJsonCases[tail](mvalue, n + 1)
case _: Unit =>
throw new MatchError(mvalue.ordinal)
}
inline def toJsonElems[Elems <: Tuple](mvalue: Mirror, n: Int = 0): String =
inline erasedValue[Elems] match {
case _: (elem *: Unit) =>
toJsonElem[elem](
mvalue.adtClass.label(mvalue.ordinal)(n + 1),
mvalue(n).asInstanceOf[elem])
case _: (elem *: tail) =>
(toJsonElem[elem](
mvalue.adtClass.label(mvalue.ordinal)(n + 1),
mvalue(n).asInstanceOf[elem])
+ ", " + toJsonElems[tail](mvalue, n + 1))
case _: Unit => ""
}
inline def toJsonElem[A](label: String, value: A): String =
implicit match {
case ev: ToJson[A] =>
s""""$label": ${ev.toJson(value)}"""
case _ =>
error(s"No ToJson instance for $value")
}
implied for ToJson[Int] {
def (value: Int) toJson: String = s"$value"
}
implied for ToJson[String] {
def (value: String) toJson: String = s""""$value""""
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment