-
Notifications
You must be signed in to change notification settings - Fork 89
[data] new Record encoding #1472
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
3e93362
af65974
630e8d3
f926d78
9dc5eab
202a489
9214523
ea12c64
bd2754d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package kyo | ||
|
|
||
| import scala.compiletime.constValue as scConstValue | ||
|
|
||
| /** An opaque type that materializes a compile-time constant value as a runtime value via an implicit given. | ||
| * | ||
| * `ConstValue[A]` is a subtype of `A`, backed by `scala.compiletime.constValue`. When `A` is a singleton literal type (e.g., `"name"`, | ||
| * `42`, `true`), summoning a `ConstValue[A]` produces the corresponding runtime value. This is useful for passing singleton type | ||
| * information to runtime code without requiring an explicit `inline` context at every call site. | ||
| * | ||
| * @tparam A | ||
| * A singleton literal type whose compile-time value is materialized at runtime | ||
| */ | ||
| opaque type ConstValue[A] <: A = A | ||
|
|
||
| object ConstValue: | ||
| /** Materializes the compile-time constant for singleton type `A` as a runtime value. */ | ||
| inline given [A]: ConstValue[A] = scConstValue[A] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package kyo | ||
|
|
||
| import Record.~ | ||
|
|
||
| /** A reified field descriptor carrying the field's singleton string name, its value type's `Tag`, and optional nested field descriptors | ||
| * (populated when the value type is itself a `Record`). | ||
| * | ||
| * Field instances are typically obtained from `Fields.fields` or constructed via the companion's `apply` method. They serve as runtime | ||
| * metadata for operations like `mapFields`, `stage`, and serialization, and also provide typed `get`/`set` accessors on records. | ||
| * | ||
| * @tparam Name | ||
| * The singleton string type of the field name | ||
| * @tparam Value | ||
| * The field's value type | ||
| */ | ||
| case class Field[Name <: String, Value]( | ||
| name: Name, | ||
| tag: Tag[Value], | ||
| nested: List[Field[?, ?]] = Nil | ||
| ): | ||
| /** Extracts this field's value from a record. Requires evidence that `F` contains `Name ~ Value`. */ | ||
| def get[F](record: Record[F])(using F <:< (Name ~ Value)): Value = | ||
| record.toDict(name).asInstanceOf[Value] | ||
|
|
||
| /** Returns a new record with this field's value replaced. Requires evidence that `F` contains `Name ~ Value`. */ | ||
| def set[F](record: Record[F], value: Value)(using F <:< (Name ~ Value)): Record[F] = | ||
| Record.init(record.toDict.update(name, value.asInstanceOf[Any])) | ||
| end Field | ||
|
|
||
| object Field: | ||
| /** Constructs a `Field` by summoning the singleton name value and `Tag` from implicit scope. */ | ||
| def apply[Name <: String & Singleton, Value](using name: ConstValue[Name], tag: Tag[Value]): Field[Name, Value] = | ||
| Field(name, tag) | ||
| end Field |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| package kyo | ||
|
|
||
| import kyo.Record.* | ||
| import scala.compiletime.* | ||
|
|
||
| /** Reifies the structure of an intersection of `Name ~ Value` field types into runtime metadata and type-level operations. | ||
| * | ||
| * Given a field type like `"name" ~ String & "age" ~ Int`, a `Fields` instance decomposes it into a tuple of individual field components | ||
| * (`("name" ~ String) *: ("age" ~ Int) *: EmptyTuple`) and provides both runtime access (field names, `Field` descriptors) and type-level | ||
| * transformations (mapping, value extraction, zipping). Also supports case class types by deriving the equivalent field intersection from | ||
| * the product's element labels and types. | ||
| * | ||
| * Instances are derived transparently via a macro (`Fields.derive`), and are summoned implicitly by `Record` operations like `map`, | ||
| * `compact`, `values`, and `zip`. | ||
| * | ||
| * @tparam A | ||
| * The field intersection type (e.g., `"name" ~ String & "age" ~ Int`) or a case class type | ||
| */ | ||
| sealed abstract class Fields[A] extends Serializable: | ||
|
|
||
| /** Tuple representation of the fields: `"a" ~ Int & "b" ~ String` becomes `("a" ~ Int) *: ("b" ~ String) *: EmptyTuple`. */ | ||
| type AsTuple <: Tuple | ||
|
|
||
| /** Applies a type constructor `F` to each field component and re-intersects the results. For example, | ||
| * `Fields["a" ~ Int & "b" ~ String].Map[Option]` yields `Option["a" ~ Int] & Option["b" ~ String]`. | ||
| */ | ||
| type Map[F[_]] = Fields.Join[Tuple.Map[AsTuple, F]] | ||
|
|
||
| /** Extracts the value types from each field component into a plain tuple: `Fields["a" ~ Int & "b" ~ String].Values` = `(Int, String)`. | ||
| */ | ||
| type Values = Fields.ExtractValues[AsTuple] | ||
|
|
||
| /** Zips this field tuple with another by name, pairing their value types into tuples. */ | ||
| type Zipped[T2 <: Tuple] = Fields.ZipValues[AsTuple, T2] | ||
|
|
||
| /** Runtime `Field` descriptors (name, tag, nested), lazily materialized. */ | ||
| lazy val fields: List[Field[?, ?]] | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only materializes fields if necessary |
||
|
|
||
| /** The set of field names, derived from `fields`. */ | ||
| def names: Set[String] = fields.iterator.map(_.name).toSet | ||
|
|
||
| end Fields | ||
|
|
||
| /** Companion providing derivation, type-level utilities, and evidence types for field operations. */ | ||
| object Fields: | ||
|
|
||
| private[kyo] def createAux[A, T <: Tuple](_fields: => List[Field[?, ?]]): Fields.Aux[A, T] = | ||
| new Fields[A]: | ||
| type AsTuple = T | ||
| lazy val fields = _fields | ||
|
|
||
| private[kyo] type Join[A <: Tuple] = Tuple.Fold[A, Any, [B, C] =>> B & C] | ||
|
|
||
| /** Match type that extracts value types from a tuple of field components into a plain tuple. */ | ||
| type ExtractValues[T <: Tuple] <: Tuple = T match | ||
| case EmptyTuple => EmptyTuple | ||
| case (n ~ v) *: rest => v *: ExtractValues[rest] | ||
|
|
||
| /** Match type that looks up the value type for field name `N` in a tuple of field components. */ | ||
| type LookupValue[N <: String, T <: Tuple] = T match | ||
| case (N ~ v) *: _ => v | ||
| case _ *: rest => LookupValue[N, rest] | ||
|
|
||
| /** Match type that zips two field tuples by name, pairing their value types into `(V1, V2)` tuples. */ | ||
| type ZipValues[T1 <: Tuple, T2 <: Tuple] = T1 match | ||
| case EmptyTuple => Any | ||
| case (n ~ v1) *: EmptyTuple => n ~ (v1, LookupValue[n, T2]) | ||
| case (n ~ v1) *: rest => (n ~ (v1, LookupValue[n, T2])) & ZipValues[rest, T2] | ||
|
|
||
| /** Refinement type alias that exposes the `AsTuple` member. */ | ||
| type Aux[A, T] = | ||
| Fields[A]: | ||
| type AsTuple = T | ||
|
|
||
| /** Macro-derived given that produces a `Fields` instance for any field intersection type or case class. */ | ||
| transparent inline given derive[A]: Fields[A] = | ||
| ${ internal.FieldsMacros.deriveImpl[A] } | ||
|
|
||
| /** Returns the `Field` descriptors for type `A`. Convenience accessor for `summon[Fields[A]].fields`. */ | ||
| def fields[A](using f: Fields[A]): List[Field[?, ?]] = f.fields | ||
|
|
||
| /** Returns the field names for type `A`. Convenience accessor for `summon[Fields[A]].names`. */ | ||
| def names[A](using f: Fields[A]): Set[String] = f.names | ||
|
|
||
| /** An opaque map from field name to a type class instance `F[Any]`, summoned inline for each field's value type. Used by operations | ||
| * like `Render` that need a type class instance per field. | ||
| */ | ||
| opaque type SummonAll[A, F[_]] = Map[String, F[Any]] | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simpler replacement for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I guess |
||
|
|
||
| extension [A, F[_]](sa: SummonAll[A, F]) | ||
| /** Retrieves the type class instance for the given field name. */ | ||
| def get(name: String): F[Any] = sa(name) | ||
|
|
||
| /** Returns true if an instance exists for the given field name. */ | ||
| def contains(name: String): Boolean = sa.contains(name) | ||
|
|
||
| /** Iterates over all (name, instance) pairs. */ | ||
| def foreach(fn: (String, F[Any]) => Unit): Unit = sa.foreach((k, v) => fn(k, v)) | ||
| end extension | ||
|
|
||
| object SummonAll: | ||
| /** Inline given that summons `F[V]` for each field `Name ~ V` in `A` and collects them into a map keyed by field name. Fails at | ||
| * compile time if any field's value type lacks an `F` instance. | ||
| */ | ||
| inline given [A, F[_]](using f: Fields[A]): SummonAll[A, F] = | ||
| summonLoop[f.AsTuple, F] | ||
|
|
||
| private inline def summonLoop[T <: Tuple, F[_]]: Map[String, F[Any]] = | ||
| inline erasedValue[T] match | ||
| case _: EmptyTuple => Map.empty | ||
| case _: ((n ~ v) *: rest) => | ||
| summonLoop[rest, F].updated(constValue[n & String], summonInline[F[v]].asInstanceOf[F[Any]]) | ||
| end SummonAll | ||
|
|
||
| /** Evidence that field type `F` contains a field named `Name`. The dependent `Value` member resolves to the field's value type. Used by | ||
| * `Record.selectDynamic` and `Record.getField` to verify field access at compile time and infer the return type. | ||
| */ | ||
| sealed abstract class Have[F, Name <: String]: | ||
| type Value | ||
|
|
||
| object Have: | ||
| private[kyo] def unsafe[F, Name <: String, V]: Have[F, Name] { type Value = V } = | ||
| new Have[F, Name]: | ||
| type Value = V | ||
|
|
||
| /** Macro-derived given that resolves the value type for `Name` in `F`. Fails at compile time if the field does not exist. */ | ||
| transparent inline given [F, Name <: String]: Have[F, Name] = | ||
| ${ internal.FieldsMacros.haveImpl[F, Name] } | ||
| end Have | ||
|
|
||
| /** Opaque evidence that all value types in field type `A` have `CanEqual` instances, enabling `==` comparisons on `Record[A]`. */ | ||
| opaque type Comparable[A] = Unit | ||
|
|
||
| object Comparable: | ||
| private[kyo] def unsafe[A]: Comparable[A] = () | ||
|
|
||
| /** Macro-derived given that verifies all field value types in `A` have `CanEqual`. Fails at compile time if any field type lacks | ||
| * `CanEqual`. | ||
| */ | ||
| transparent inline given derive[A]: Comparable[A] = | ||
| ${ internal.FieldsMacros.comparableImpl[A] } | ||
| end Comparable | ||
|
|
||
| end Fields | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the replacement for
TypeIntersection