Last active
March 10, 2022 09:36
-
-
Save tarao/297dd72900d05a669ddc9a72b98c4b42 to your computer and use it in GitHub Desktop.
A simple extensible record implementation in Scala 3
This file contains hidden or 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
scalaVersion := "3.1.1" |
This file contains hidden or 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
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} | |
} |
This file contains hidden or 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
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) } | |
} | |
} |
This file contains hidden or 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
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) } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
References