Conversation
| * @tparam A | ||
| * The field intersection type (e.g., `"name" ~ String & "age" ~ Int`) or a case class type | ||
| */ | ||
| sealed abstract class Fields[A] extends Serializable: |
There was a problem hiding this comment.
This is the replacement for TypeIntersection
| type Zipped[T2 <: Tuple] = Fields.ZipValues[AsTuple, T2] | ||
|
|
||
| /** Runtime `Field` descriptors (name, tag, nested), lazily materialized. */ | ||
| lazy val fields: List[Field[?, ?]] |
There was a problem hiding this comment.
only materializes fields if necessary
| /** 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]] |
There was a problem hiding this comment.
Simpler replacement for Inliner
There was a problem hiding this comment.
Yeah, I guess Inliner was slightly more powerful, but this api (without having to extend class) is much more user friendly
| private inline def summonLoop[T <: Tuple, F[_]]: Map[String, F[Any]] = | ||
| inline erasedValue[T] match | ||
| case _: EmptyTuple => Map.empty | ||
| case _: ((n1 ~ v1) *: (n2 ~ v2) *: (n3 ~ v3) *: (n4 ~ v4) *: rest) => |
There was a problem hiding this comment.
all inline loops are unrolled to avoid stack reaching the inlining limit
| * Intersection of `Name ~ Value` field types describing the record's schema | ||
| */ | ||
| final class Record[+Fields] private (val toMap: Map[Field[?, ?], Any]) extends AnyVal with Dynamic: | ||
| final class Record[F](private[kyo] val dict: Dict[String, Any]) extends Dynamic: |
There was a problem hiding this comment.
Ideally this should be opaque type Record[F] = Dict[String, Any] but I couldn't find a way to make it work with Dynamic. I've removed AnyVal since it can actually introduce allocations when used in generic contexts, which is the case for anon functions in Scala 3 due to the lack of specialization.
| * instances per field. Call the returned `StageOps` directly to stage without a type class, or chain `.using[TC]` to require a type | ||
| * class instance for each field's value type. | ||
| */ | ||
| inline def stage[A](using f: Fields[A]): StageOps[A, f.AsTuple] = new StageOps(()) |
There was a problem hiding this comment.
Staging is simplified to regular methods instead of having to extend a class
|
@road21 you might be interested in this! :) |
| end hashCode | ||
|
|
||
| /** Returns a human-readable string representation in the form `"name ~ Alice & age ~ 30"`. */ | ||
| def show: String = |
There was a problem hiding this comment.
minor: should we call this render?
Also, if dict.mkString accepted a separator for k/v this wouldn't need to be duplicated.
There was a problem hiding this comment.
ah, we provide a Render evidence, so asText is already available. I'll remove this method
| dict.is(other.dict) | ||
| case _ => false | ||
|
|
||
| override def hashCode(): Int = |
There was a problem hiding this comment.
Is this necessary due to Span? Perhaps we should have Span.hashcode anyways (that doesn't just return obj reference)?
There was a problem hiding this comment.
Yep. We can't implement hashCode for Span since it's an opaque type pointing to Array
6d538be to
ea12c64
Compare
Recordhas a major drawback: fields can be duplicated in the same record with different types. Ideally"myField" ~ Int & "myField" ~ Stringwould be equivalent at the type level to"myField" ~ (Int | String)but, at the time when I introducedRecord, I couldn't find a type-level solution for it so I decided to just support the duplicate fields and save aTagto disambiguate at runtime.I've been working on the new
kyo-httpmodule and decided to useRecordinstead of named tuples but the limitation isn't acceptable. There's the additional memory footprint to save the tags and allowing duplicate fields introduces some rough edge cases.In this new encoding, I'm using a type-level workaround: keep
Recordinvariant but provide subtype relationship via aConversion. It isn't ideal but works well and produces the expected reduction at the type level. I'm also usingDictto reduce allocations further. Besides the memory footprint reduction and proper field tracking behavior, I simplified most of the APIs and implementations.