Created
September 22, 2024 07:55
-
-
Save bench87/83ec49fcf093b95bcf59d8137e0707fb to your computer and use it in GitHub Desktop.
Inline your boilerplate – harnessing Scala 3 metaprogramming without macros
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" | |
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