Skip to content

Instantly share code, notes, and snippets.

@tarao
Last active March 10, 2022 09:36
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 tarao/297dd72900d05a669ddc9a72b98c4b42 to your computer and use it in GitHub Desktop.
Save tarao/297dd72900d05a669ddc9a72b98c4b42 to your computer and use it in GitHub Desktop.
A simple extensible record implementation in Scala 3
scalaVersion := "3.1.1"
import record.Record
@main def main() = {
val r1 = Record.empty
println(util.showTypeOf(r1)) // record.Record
val r2 = r1 + ("name" -> "tarao")
println(util.showTypeOf(r2)) // record.Record { val name: String }
println(r2.name) // tarao
// println(r2.age)
// ^^^^^^
// value age is not a member of record.Record{name: String}
// val t = ("age" -> 37)
// val r3 = r2 + t
// ^
// a tuple (_, _) or an arrow _ -> _ required
// val label = "age"
// val r3 = r2 + (label -> 37)
// ^^^^^
// a string literal required
val r3 = r2 + ("age" -> 37)
println(util.showTypeOf(r3)) // record.Record { val name: String; val age: Int }
println(r3.name) // tarao
println(r3.age) // 37
// println(r3.job)
// ^^^^^^
// value job is not a member of record.Record{name: String; age: Int}
}
package record
class Record private (elems: (String, Any)*) extends Selectable {
private val _fields = elems.toMap
def selectDynamic(name: String): Any = _fields(name)
private def _add(elem: (String, Any)): Record =
new Record(elem +: _fields.toSeq: _*)
}
object Record {
val empty = new Record()
import scala.compiletime.{codeOf, error}
import scala.quoted.*
def addImpl[R <: Record: Type, S <: String: Type, V: Type](
record: Expr[R],
expr: Expr[(S, V)],
)(using Quotes): Expr[Any] = {
import quotes.reflect.*
val Some(labelExpr) = expr match {
case '{( ${l}: S ) -> $_} => Some(l)
case '{($l, $_)} => Some(l)
case _ =>
report.error("a tuple (_, _) or an arrow _ -> _ required", expr)
None
}
val Some(label) = labelExpr.asTerm match {
case Literal(StringConstant(label)) => Some(label)
case _ =>
report.error("a string literal required", labelExpr)
None
}
Refinement(TypeRepr.of[R], label, TypeRepr.of[V]).asType match {
case '[tpe] => '{$record._add($expr).asInstanceOf[tpe]}
}
}
extension[R <: Record](r: R) {
transparent inline def +[S <: String, V](inline elem: (S, V)) =
${ addImpl('r, 'elem) }
}
}
package util
import scala.quoted.*
def showTypeOfImpl[A: Type](x: Expr[A])(using Quotes): Expr[String] = {
import quotes.reflect.*
val tpe = TypeTree.of[A].show(using Printer.TreeShortCode)
Expr(tpe)
}
inline def showTypeOf[A](x: A): String =
${ showTypeOfImpl('x) }