Now that Named Tuples may become a reality, I want to bring up a pain point that this feature can help solve.
The problem with structural types
Currently structural types are very limiting. The only way to get a refinement is through casting and requires every function that transforms the refined object to keep the refinement along the way.
For example, say I have a class Foo[T]. I need to have specific fields available (at compile-time) for instances of Foo according to the type T. Structural types provide no such functionality.
Solving the problem via Selectable and Implicit Conversions
The only way to achieve this is by extending Selectable and creating an ugly implicit conversion and a helper macro like so:
final class Foo[T] extends Selectable:
def selectDynamic(name: String): Any = /* runtime field selection */
given [T](using r: Refiner[T]): Conversion[Foo[T], r.Out] = _.asInstanceOf[r.Out] //just forcing the compiler to cooperate
trait Refiner[T]{ type Out <: Foo[T] }
object Refiner:
transparent inline given [T]: Refiner[T] = ${ refineMacro[T] }
def refineMacro[T](using Quotes, Type[T]): Expr[Refiner[T]] =
import quotes.reflect.*
val tTpe = TypeRepr.of[T]
val fooTpe = TypeRepr.of[Foo[T]]
val fields: List[(String, TypeRepr)] = /*your field logic here*/
val refined = fields.foldLeft(fooTpe) { case (r, (n, t)) => Refinement(r, n, t) }
val refinedType = refined.asTypeOf[Foo[T]]
'{ new Refiner[T]{ type Out = refinedType.Underlying }}
end refineMacro
end Refiner
This is clearly ugly, relies on a clunky implicit conversion mechanism (like implicit classes did) and also fails to support field auto-completion by the IDE because the IDE cannot know what fields are actually available.
Proposal: Expanding Selectable with a Named Tuple Fields type member
We add to Selectable a Tuple type member Fields.
trait Selectable:
type Fields <: Tuple
Rules:
- If
Fieldsis unbounded further by the user (the upper bound remainsTuple), then the current behavior of Selectable extended objects remains the same. (If the user just bounds withNonEmptyTuplewe treat it the same also). - If
Fieldsis anEmptyTuple, there are no selectable fields available and we should get a compiler error when trying to select a field that does not already exist. - If
Fieldsis some kind of non-zero Tuple arity, then the available fields and their types will be defined by the tuple type. This should work with both named and unnamed tuples just the same, but of course generally more useful with named tuples.