Last active
June 20, 2024 22:47
-
-
Save Iltotore/eece20188d383f7aee16a0b89eeb887f to your computer and use it in GitHub Desktop.
Mirror-like union and intersection types derivation
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
//> 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)) |
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
//> 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]] |
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
//> 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)) |
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
//> 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]] |
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
//> 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]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
scala-cli intersection.sc
: run intersection examplescala-cli union.sc
: run union examplescala-cli ziojson.sc
run union example with zio-json's JsonDecoder