I was thinking:
With a compile-time version of selectDynamic
, we could get most named tuple features for free"
Well, it turns out we do have that: an inline
selectDynamic
So here is an implementation of named pairs with a friendly syntax (for terms) and type safety:
import language.dynamics
@main def testPair: Unit =
val john = P(age = 42, name = "John")
john.age
john.name
//john.address // error: Field does not exist
type Person = NamedPair["age", Int, "name", String]
val bill: Person = (3, "Bill")
val john2: Person = john
type LabelType = String & Singleton
// Named pair constructor
object P extends Dynamic:
transparent inline def applyDynamicNamed[LA <: LabelType, A, LB <: LabelType, B, T <: Tuple](inline s: String)(inline a: (LA, A), b: (LB, B)): Any =
inline s match
case "apply" =>
NamedPair[LA, A, LB, B](a._2, b._2)
case _ =>
compiletime.error(s"Method $s does not exist on P")
class NamedPair[LabelA <: LabelType, A, LabelB <: LabelType, B](val _1: A, val _2: B) extends Dynamic:
transparent inline def selectDynamic(inline s: String) =
inline s match
case _ : LabelA => _1
case _ : LabelB => _2
case _ =>
compiletime.error(s"Field $s does not exist on NamedPair")
given [LabelA <: LabelType, A, LabelB <: LabelType, B]: Conversion[(A, B), NamedPair[LabelA, A, LabelB, B]] = (a, b) => NamedPair(a, b)
We thus have a library handling all tuples of arities 2 to 22/11, which is a good start !
I tried making an implementation which can handle the remaining arities, but I was not good enough a tuple wrangling to make it work:
Unfold
import language.dynamics
type ExtractValue[P <: (String, ?)] = P match
case (label, value) => value
type ExtractValues[T <: Tuple] = T match
case p *: tail => ExtractValue[p] *: ExtractValues[tail]
case EmptyTuple => EmptyTuple
type ExtractLabels[T <: Tuple] = T match
case (label, value) *: tail => label *: ExtractLabels[tail]
case EmptyTuple => EmptyTuple
object T extends Dynamic:
transparent inline def applyDynamicNamed[T <: Tuple](inline s: String)(inline elements: T): Any =
inline s match
case "apply" =>
def tmp[P](p: P): ExtractValue[P] = p match
case (label, value) => value
val fields: ExtractValues[T] = elements.map[ExtractValue]{[P] => (p: P) => tmp(p)}
NamedTuple[ExtractLabels[T], ExtractValues[T]](fields)
case _ =>
compiletime.error("Unsupported method")
class NamedTuple[Labels <: Tuple, Values <: Tuple](val fields: Values) extends Dynamic:
transparent inline def selectDynamic(inline s: String) =
inline s match
case valueOf[] =>
case _ =>
compiletime.error("Field does not exist")
@main def hello: Unit =
val john = T(age = 42, name = "John")
john.age
john.name
//john.address // error: Field does not exist
There might be a way to make NamedPair
work as an alias, and get some kind of extension
applyDynamicNamed
, but I haven’t tried it yet
I have a strong feeling we can develop a strict, and easily macro-manipulable, type which can then be elegantly used by the user, and all with no or minimal addition to the language