Skip to content

Instantly share code, notes, and snippets.

@bench87
Created September 22, 2024 07:55
Show Gist options
  • Save bench87/83ec49fcf093b95bcf59d8137e0707fb to your computer and use it in GitHub Desktop.
Save bench87/83ec49fcf093b95bcf59d8137e0707fb to your computer and use it in GitHub Desktop.
Inline your boilerplate – harnessing Scala 3 metaprogramming without macros
//> using scala "3"
import scala.compiletime.{erasedValue, summonInline, constValue, error}
import scala.deriving.Mirror
import scala.collection.Factory
import scala.annotation.implicitNotFound
import scala.util.NotGiven
import Field.TypeForLabel
infix type =:!=[A, B] = NotGiven[A =:= B]
type FieldExists[Label <: String, ToSubcases <: Tuple] =
Field.TypeForLabel[Label, ToSubcases] =:!= Nothing
type VerifyTypes[FieldType, Label <: String, ToSubcases <: Tuple] =
Field.TypeForLabel[Label, ToSubcases] =:= FieldType
sealed trait Field[Label <: String, Type]
object Field:
type FromLabelsAndTypes[Labels <: Tuple, Types <: Tuple] <: Tuple =
(Labels, Types) match
case (EmptyTuple, EmptyTuple) => EmptyTuple
case (labelHead *: labelTail, typeHead *: typeTail) =>
Field[labelHead, typeHead] *: FromLabelsAndTypes[labelTail, typeTail]
type TypeForLabel[Label <: String, Fields <: Tuple] =
Fields match
case Field[Label, tpe] *: _ => tpe
case _ *: tail => TypeForLabel[Label, tail]
case EmptyTuple => Nothing
type DropByLabel[Label <: String, Fields <: Tuple] <: Tuple =
Fields match
case EmptyTuple => EmptyTuple
case Field[Label, _] *: tail => tail
case head *: tail => head *: DropByLabel[Label, tail]
opaque type FieldName = String
object FieldName:
inline def fromLiteralLabel[Label <: String]: FieldName =
constValue[Label]
def wrapAll[K, V](map: Map[K, V]): Map[String, V] =
map.asInstanceOf[Map[String, V]]
// Transformer type class
trait Transformer[From, To]:
def transform(from: From): To
object Transformer:
def apply[A, B](using transformer: Transformer[A, B]): Transformer[A, B] =
transformer
// Identity Transformer
given [A]: Transformer[A, A] with
def transform(from: A): A = from
// A to Option[B] Transformer
given [A, B](using Transformer[A, B]): Transformer[A, Option[B]] with
def transform(from: A): Option[B] = Some(Transformer[A, B].transform(from))
// Option[A] to Option[B] Transformer
given [A, B](using Transformer[A, B]): Transformer[Option[A], Option[B]] with
def transform(from: Option[A]): Option[B] =
from.map(Transformer[A, B].transform)
// Cellection Transformer
given [A, B, CollFrom[+E] <: Iterable[E], CollTo[+E] <: Iterable[E]](using
transformer: Transformer[A, B],
factory: Factory[B, CollTo[B]]
): Transformer[CollFrom[A], CollTo[B]] with
def transform(from: CollFrom[A]): CollTo[B] =
from
.foldLeft(factory.newBuilder)((builder, a) => builder += transformer.transform(a))
.result()
extension [A <: Product](from: A) {
def to[B](using Transformer[A, B]): B = Transformer[A, B].transform(from)
inline def into[B <: Product](using
A: Mirror.ProductOf[A],
B: Mirror.ProductOf[B]
) = TransformerBuilder.create(from)(using A, B)
}
object Derivation:
inline def labels[Labels <: Tuple]: List[FieldName] =
inline erasedValue[Labels] match
case _: EmptyTuple => List.empty
case _: (h *: t) =>
constValue[h].asInstanceOf[FieldName] :: labels[t]
inline def transformerForField[ToLabel <: String, ToType, FromFields <: Tuple]: (FieldName, Transformer[?, ?]) =
inline erasedValue[FromFields] match
case _: EmptyTuple =>
error(s"Transformer not found for field ${constValue[ToLabel]}")
case _: (Field[ToLabel, fromType] *: _) =>
FieldName.fromLiteralLabel[ToLabel] -> summonInline[Transformer[fromType, ToType]]
case _: (_ *: tail) =>
transformerForField[ToLabel, ToType, tail]
inline def transformersForAllFields[FromFields <: Tuple, ToFields <: Tuple]: Map[FieldName, Transformer[?, ?]] =
inline erasedValue[ToFields] match
case _: EmptyTuple =>
Map.empty
case _: (Field[label, tpe] *: tail) =>
transformersForAllFields[FromFields, tail] + transformerForField[label, tpe, FromFields]
inline def unsafeConstructInstance[To](from: Product)(unsafeMapper: (Map[String, ?], FieldName) => ?)(using To: Mirror.ProductOf[To]): To =
val labelsToValuesOfFrom: Map[String, Any] = FieldName.wrapAll(from.productElementNames.zip(from.productIterator).toMap)
val labelIndicesOfTo: Map[FieldName, Int] = labels[To.MirroredElemLabels].zipWithIndex.toMap
val valueArrayOfTo: Array[Any] = new Array[Any](labelIndicesOfTo.size)
labelIndicesOfTo.foreach: (label: FieldName, idx: Int) =>
val valueForLabel = unsafeMapper(labelsToValuesOfFrom, label)
valueArrayOfTo.update(idx, valueForLabel)
To.fromProduct(Tuple.fromArray(valueArrayOfTo))
inline given [From <: Product, To <: Product](using A: Mirror.ProductOf[From], B: Mirror.ProductOf[To]): Transformer[From, To] =
new Transformer[From, To]:
def transform(from: From): To =
val transformers = transformersForAllFields[
Field.FromLabelsAndTypes[A.MirroredElemLabels, A.MirroredElemTypes],
Field.FromLabelsAndTypes[B.MirroredElemLabels, B.MirroredElemTypes]
]
unsafeConstructInstance(from) { (labelsToValuesOfA, label) =>
transformers(label)
.asInstanceOf[Transformer[Any, Any]]
.transform(labelsToValuesOfA(label.toString))
}
final case class TransformerBuilder[From <: Product, To <: Product, FromSubcases <: Tuple, ToSubcases <: Tuple, DerivedFromSubcases <: Tuple, DerivedToSubcases <: Tuple](
appliedTo: From,
computeds: Map[FieldName, From => Any],
constants: Map[FieldName, Any],
renameTransformers: Map[FieldName, (String, Transformer[Any, Any])]
):
import Field.*
inline def withFieldConst[Label <: String]: WithFieldConstHelper[Label, From, To, FromSubcases, ToSubcases, DerivedFromSubcases, DerivedToSubcases] =
new WithFieldConstHelper[Label, From, To, FromSubcases, ToSubcases, DerivedFromSubcases, DerivedToSubcases](this)
inline def withFieldComputed[Label <: String](
func: From => Any
)(using
ev1: FieldExists[Label, ToSubcases]
): TransformerBuilder[From, To, FromSubcases, ToSubcases, Field.DropByLabel[Label, DerivedFromSubcases], Field.DropByLabel[Label, DerivedToSubcases]] =
new TransformerBuilder[From, To, FromSubcases, ToSubcases, Field.DropByLabel[Label, DerivedFromSubcases], Field.DropByLabel[Label, DerivedToSubcases]](
appliedTo = appliedTo,
computeds = computeds + (FieldName.fromLiteralLabel[Label] -> func),
constants = constants,
renameTransformers = renameTransformers
)
final class WithFieldConstHelper[Label <: String, F <: Product, T <: Product, FS <: Tuple, TS <: Tuple, DFS <: Tuple, DTS <: Tuple](
tb: TransformerBuilder[F, T, FS, TS, DFS, DTS]
):
inline def apply[Value](const: Value)(using
@implicitNotFound("${T} doesn't seem to have a field named ${Label}")
ev1: FieldExists[Label, TS],
@implicitNotFound("${Value} is not a valid type for field ${Label} in ${T}")
ev2: VerifyTypes[Value, Label, TS]
): TransformerBuilder[F, T, FS, TS, DropByLabel[Label, DFS], DropByLabel[Label, DTS]] =
new TransformerBuilder(
appliedTo = tb.appliedTo,
computeds = tb.computeds,
constants = tb.constants + (FieldName.fromLiteralLabel[Label] -> const),
renameTransformers = tb.renameTransformers
)
inline def withFieldRenamed[FromLabel <: String, ToLabel <: String](using
@implicitNotFound("${From} doesn't seem to have a field named ${FromLabel}")
ev1: FieldExists[FromLabel, FromSubcases],
@implicitNotFound("${To} doesn't seem to have a field named ${ToLabel}")
ev2: FieldExists[ToLabel, ToSubcases],
transformer: Transformer[
Field.TypeForLabel[FromLabel, FromSubcases],
Field.TypeForLabel[ToLabel, ToSubcases]
]
): TransformerBuilder[
From,
To,
FromSubcases,
ToSubcases,
Field.DropByLabel[ToLabel, DerivedFromSubcases],
Field.DropByLabel[ToLabel, DerivedToSubcases]
] =
new TransformerBuilder(
appliedTo = appliedTo,
computeds = computeds,
constants = constants,
renameTransformers = renameTransformers + (
FieldName.fromLiteralLabel[ToLabel] ->
(constValue[FromLabel], transformer.asInstanceOf[Transformer[Any, Any]])
)
)
final class WithFieldComputedHelper[Label <: String]:
inline def apply[Value](func: From => Value)(using
@implicitNotFound("${To} doesn't seem to have a field named ${Label}")
ev1: FieldExists[Label, ToSubcases],
@implicitNotFound("${Value} is not a valid type for field ${Label} in ${To}")
ev2: VerifyTypes[Value, Label, ToSubcases]
): TransformerBuilder[
From,
To,
FromSubcases,
ToSubcases,
DropByLabel[Label, DerivedFromSubcases],
DropByLabel[Label, DerivedToSubcases]
] =
new TransformerBuilder(
appliedTo = appliedTo,
computeds = computeds + (FieldName.fromLiteralLabel[Label] -> func),
constants = constants,
renameTransformers = renameTransformers
)
inline def withFieldComputed[Label <: String]: WithFieldComputedHelper[Label] =
new WithFieldComputedHelper[Label]
inline def build(using Mirror.ProductOf[To]): Transformer[From, To] =
new Transformer[From, To]:
def transform(from: From): To =
val transformers = Derivation.transformersForAllFields[DerivedFromSubcases, DerivedToSubcases]
Derivation.unsafeConstructInstance[To](from): (labelsToValuesOfFrom, label) =>
def erase(transformer: Transformer[?, ?]) = transformer.asInstanceOf[Transformer[Any, Any]]
def maybeValueFromRename =
renameTransformers
.get(label)
.map:
case (fromLabel, transformer) =>
erase(transformer).transform(labelsToValuesOfFrom(fromLabel))
def maybeValueFromDerived =
transformers
.get(label)
.map(erase)
.map(_.transform(labelsToValuesOfFrom(label.toString)))
def maybeValueFromComputed =
computeds.get(label).map(f => f(from))
maybeValueFromRename
.orElse(maybeValueFromDerived)
.orElse(maybeValueFromComputed)
.getOrElse(constants(label))
inline def transform(using Mirror.ProductOf[To]): To =
build.transform(appliedTo)
object TransformerBuilder:
import Field.*
inline def create[From <: Product, To <: Product](
from: From
)(using
A: Mirror.ProductOf[From],
B: Mirror.ProductOf[To]
): TransformerBuilder[
From,
To,
FromLabelsAndTypes[A.MirroredElemLabels, A.MirroredElemTypes],
FromLabelsAndTypes[B.MirroredElemLabels, B.MirroredElemTypes],
FromLabelsAndTypes[A.MirroredElemLabels, A.MirroredElemTypes],
FromLabelsAndTypes[B.MirroredElemLabels, B.MirroredElemTypes]
] = new TransformerBuilder(
appliedTo = from,
computeds = Map.empty,
constants = Map.empty,
renameTransformers = Map.empty
)
case class Person(lastName: String, firstName: String, age: Int)
case class PersonButMoreFields(
firstName: String,
lastName: String,
age: Int,
socialSecurityNumber: String
)
case class PersonButMoreFields2(
name: String,
lastName: String,
age: Int,
socialSecurityNumber: String
)
@main def demo =
import Transformer._
import Derivation.given
val person = Person("John", "Doe", 50)
val personWithMoreFields =
PersonButMoreFields("Mark", "Dunk", 23, "SSN-123456")
val fromPersonWithMoreToPerson: Person = personWithMoreFields.to[Person]
personWithMoreFields.into[Person]
println(fromPersonWithMoreToPerson)
val transformedPerson = person
.into[PersonButMoreFields]
.withFieldConst["socialSecurityNumber"]("SSN-123456")
.transform
println(transformedPerson)
val transformedComputed = person
.into[PersonButMoreFields]
.withFieldComputed["socialSecurityNumber"](_.age.toString)
.transform
println(transformedComputed)
val transformedRenamed = person
.into[PersonButMoreFields2]
.withFieldConst["socialSecurityNumber"]("SSN-123456")
.withFieldRenamed["firstName", "name"]
.transform
println(transformedRenamed)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment