Skip to content

Instantly share code, notes, and snippets.

@Iltotore
Last active July 31, 2023 05:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Iltotore/eece20188d383f7aee16a0b89eeb887f to your computer and use it in GitHub Desktop.
Save Iltotore/eece20188d383f7aee16a0b89eeb887f to your computer and use it in GitHub Desktop.
Mirror-like union and intersection types derivation
//> using scala "3.2.0"
//> using file "IntersectionTypeMirror.scala"
import IntersectionTypeMirror.given
import scala.compiletime.{erasedValue, summonInline}
import scala.reflect.TypeTest
trait Show[A]:
def showValue(value: A): String
trait FooA
trait FooB
class FooC extends FooA with FooB
given Show[FooA] = _ => "A" //I have no idea for these instances =p
given Show[FooB] = _ => "B"
//See https://docs.scala-lang.org/scala3/reference/contextual/derivation.html#how-to-write-a-type-class-derived-method-using-low-level-mechanisms
inline def summonInstances[A <: Tuple]: List[Show[?]] =
inline erasedValue[A] match
case _: EmptyTuple => Nil
case _: (head *: tail) => summonInline[Show[head]] :: summonInstances[tail]
inline given derivedIntersection[A](using inline inter: IntersectionTypeMirror[A]): Show[A] =
val instances = summonInstances[inter.ElementTypes] //Show instances
(value: A) => instances.map(_.asInstanceOf[Show[A]].showValue(value)).mkString(" aka ")
val x: FooA & FooB = FooC()
println(summon[Show[FooA & FooB]].showValue(x))
//> using scala "3.2.0"
import scala.quoted.Quotes
import scala.annotation.implicitNotFound
import scala.quoted.*
import scala.collection.View.Empty
trait IntersectionTypeMirror[A]:
type ElementTypes <: Tuple
class IntersectionTypeMirrorImpl[A, T <: Tuple] extends IntersectionTypeMirror[A]: //A class is more convenient to instantiate using macros
override type ElementTypes = T
object IntersectionTypeMirror:
transparent inline given derived[A]: IntersectionTypeMirror[A] = ${derivedImpl[A]}
private def derivedImpl[A](using Quotes, Type[A]): Expr[IntersectionTypeMirror[A]] =
import quotes.reflect.*
val tplPrependType = TypeRepr.of[? *: ?]
val tplConcatType = TypeRepr.of[Tuple.Concat]
def prependTypes(head: TypeRepr, tail: TypeRepr): TypeRepr =
AppliedType(tplPrependType, List(head, tail))
def concatTypes(left: TypeRepr, right: TypeRepr): TypeRepr =
AppliedType(tplConcatType, List(left, right))
def rec(tpe: TypeRepr): TypeRepr =
tpe.dealias match
case AndType(left, right) => concatTypes(rec(left), rec(right))
case t => prependTypes(t, TypeRepr.of[EmptyTuple])
val tupled =
TypeRepr.of[A].dealias match
case and: AndType => rec(and).asType.asInstanceOf[Type[Elems]]
case tpe => report.errorAndAbort(s"${tpe.show} is not an intersection type")
type Elems
given Type[Elems] = tupled
Apply( //Passing the type using quotations causes the type to not be inlined
TypeApply(
Select.unique(
New(
Applied(
TypeTree.of[IntersectionTypeMirrorImpl],
List(
TypeTree.of[A],
TypeTree.of[Elems]
)
)
),
"<init>"
),
List(
TypeTree.of[A],
TypeTree.of[Elems]
)
),
Nil
).asExprOf[IntersectionTypeMirror[A]]
//> using scala "3.2.0"
//> using file "UnionTypeMirror.scala"
import UnionTypeMirror.given
import scala.compiletime.{erasedValue, summonInline}
import scala.reflect.TypeTest
trait Show[A]:
def showValue(value: A): String
given Show[String] = value => s"str: $value"
given Show[Int] = value => s"int: $value"
given Show[Boolean] = value => s"boolean: $value"
//See https://docs.scala-lang.org/scala3/reference/contextual/derivation.html#how-to-write-a-type-class-derived-method-using-low-level-mechanisms
inline def summonInstances[A <: Tuple]: List[Show[?]] =
inline erasedValue[A] match
case _: EmptyTuple => Nil
case _: (head *: tail) => summonInline[Show[head]] :: summonInstances[tail]
inline def summonTypeTests[A <: Tuple]: List[TypeTest[Any, ?]] =
inline erasedValue[A] match
case _: EmptyTuple => Nil
case _: (head *: tail) => summonInline[TypeTest[Any, head]] :: summonTypeTests[tail]
inline given derivedUnion[A](using inline union: UnionTypeMirror[A]): Show[A] =
val instances = summonInstances[union.ElementTypes] //Show instances
val tests = summonTypeTests[union.ElementTypes] //Typetests to test against each type at runtime
val testWithInstance = tests.zip(instances)
(value: A) =>
testWithInstance
.collectFirst { case (t, i) if t.unapply(value).isDefined => i }
.get
.asInstanceOf[Show[A]]
.showValue(value)
val show = summon[Show[Int | String | Boolean]]
println(show.showValue(5))
println(show.showValue("a"))
println(show.showValue(true))
//> using scala "3.2.0"
import scala.quoted.Quotes
import scala.annotation.implicitNotFound
import scala.quoted.*
import scala.collection.View.Empty
trait UnionTypeMirror[A]:
type ElementTypes <: Tuple
class UnionTypeMirrorImpl[A, T <: Tuple] extends UnionTypeMirror[A]: //A class is more convenient to instantiate using macros
override type ElementTypes = T
object UnionTypeMirror:
transparent inline given derived[A]: UnionTypeMirror[A] = ${derivedImpl[A]}
private def derivedImpl[A](using Quotes, Type[A]): Expr[UnionTypeMirror[A]] =
import quotes.reflect.*
val tplPrependType = TypeRepr.of[? *: ?]
val tplConcatType = TypeRepr.of[Tuple.Concat]
def prependTypes(head: TypeRepr, tail: TypeRepr): TypeRepr =
AppliedType(tplPrependType, List(head, tail))
def concatTypes(left: TypeRepr, right: TypeRepr): TypeRepr =
AppliedType(tplConcatType, List(left, right))
def rec(tpe: TypeRepr): TypeRepr =
tpe.dealias match
case OrType(left, right) => concatTypes(rec(left), rec(right))
case t => prependTypes(t, TypeRepr.of[EmptyTuple])
val tupled =
TypeRepr.of[A].dealias match
case or: OrType => rec(or).asType.asInstanceOf[Type[Elems]]
case tpe => report.errorAndAbort(s"${tpe.show} is not a union type")
type Elems
given Type[Elems] = tupled
Apply( //Passing the type using quotations causes the type to not be inlined
TypeApply(
Select.unique(
New(
Applied(
TypeTree.of[UnionTypeMirrorImpl],
List(
TypeTree.of[A],
TypeTree.of[Elems]
)
)
),
"<init>"
),
List(
TypeTree.of[A],
TypeTree.of[Elems]
)
),
Nil
).asExprOf[UnionTypeMirror[A]]
//> using scala "3.2.0"
//> using lib "dev.zio::zio-json:0.3.0"
//> using file "UnionTypeMirror.scala"
import UnionTypeMirror.given
import scala.compiletime.{erasedValue, summonInline}
import zio.json.*
//Yet another union derivation example using ZIO's JsonDecoder
inline def summonDecoders[A <: Tuple]: List[JsonDecoder[Any]] =
inline erasedValue[A] match
case _: EmptyTuple => Nil
case _: (head *: tail) => summonInline[JsonDecoder[head]].asInstanceOf[JsonDecoder[Any]] :: summonDecoders[tail]
inline given [A](using union: UnionTypeMirror[A]): JsonDecoder[A] =
summonDecoders[union.ElementTypes].reduce(_ <> _).asInstanceOf[JsonDecoder[A]]
case class Color(value: Int | String)
given JsonDecoder[Color] = DeriveJsonDecoder.gen
println("""{"value":633}""".fromJson[Color])
println("""{"value":"FFF"}""".fromJson[Color])
@Iltotore
Copy link
Author

  • scala-cli intersection.sc: run intersection example
  • scala-cli union.sc: run union example
  • scala-cli ziojson.sc run union example with zio-json's JsonDecoder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment