Trying to understand if I get the match types totally wrong.
I constantly end up with snippets of code where I have to coerce run-time representation to match types. I’m (kind of) certain that the run-time matches the out type and it does what I want it to do, but I have a feeling that I’m defeating the whole purpose of static types.
Here’s one example. Particularly lengthy, but also the one where I’m less sure I do the right thing.
// I have a tuple of `Typed` known at compile time and I want to add a constrain only to some of them
// The type-level part is ok
/**
* @tparam A is actual value
* @tparam Label is statically known label
* @tparam C is a statically known list of constraints (typelevel set)
* */
case class Typed[A, Label <: String & Singleton, C <: Tuple](a: A, label: Label, constraints: C)
/** List of labels I want to process */
type LabelsToConstrain = "bar" *: EmptyTuple
def labelsToConstrain: LabelsToConstrain = "bar" *: EmptyTuple
type Fields = Typed[Int, "wow", EmptyTuple] *: Typed[Int, "bar", EmptyTuple] *: EmptyTuple
val fields: Fields = Typed(3, "wow", EmptyTuple) *: Typed(42, "bar", EmptyTuple) *: EmptyTuple
/** Type-level function adding a constrain only to `Typed` fields that have matching label */
type AddUnique[L <: Singleton, T] = T match
case Typed[a, l, c] =>
L == l match
case true => Typed[a, l, "unique" *: c]
case false => Typed[a, l, c]
/** The main funciton, accepting list of labels and list of fields */
type Constrain[Labels <: Tuple, TypedFields <: Tuple] =
(Labels, TypedFields) match
case (EmptyTuple, fields) => fields
case (label *: tail, types) => Constrain[tail, Map[types, [t] =>> AddUnique[label, t]]]
// This works as expected
def run: Constrain[LabelsToConstrain, Fields] =
constrain(labelsToConstrain, fields)
// But below, everything is riddled with `asInstanceOf`
def constrain[Labels <: Tuple, Types <: Tuple](labels: Labels, types: Types): Constrain[Labels, Types] =
(labels, types) match
case (EmptyTuple, t) => t.asInstanceOf[Constrain[Labels, Types]]
case (label *: tail, t) => constrain(tail.asInstanceOf[Labels], t.map([a] => (a: a) => addUnique(label, a)).asInstanceOf[Types])
def addUnique[L <: Singleton, T](l: L, t: T): AddUnique[L, T] =
t match
case t: Typed[a, l, c] if t.label == l =>
t.copy(constraints = "unique" *: t.constraints).asInstanceOf[AddUnique[L, T]]
case t: Typed[a, l, c] =>
t.asInstanceOf[AddUnique[L, T]]
Every time I end up with code like that - it feels I’d be better to go with a macro.