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.