Is it okay to use asInstanceOf together with match types

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.

At some point I thought polymorphic functions can help out here and rewrote my addUnique as following:

  val addUnique: [L <: Singleton, T] => (L, T) => AddUnique[L, T] =
    [L <: Singleton, T] => (l: L, t: T) => t match
      case t: Typed[a, l, c] if t.label == l =>
        t.copy(constraints = "unique" *: t.constraints)
      case t: Typed[a, l, c] =>
        t

And it does help for some boilerplate-cleaning, but doesn’t help with casting.

1 Like

Thanks, @DmytroMitin! It opens my eyes a little bit - apparently match types are not that magical as I thought, I missed the “special mode of typing for match expressions” part in docs. I’m trying to fix my match types, but thinking I’m actually going to give up and just switch to macros. This is somehow a very common sentiment in my work with new metaprogramming capabilities:

At the beginning: I can implement X thing with compiletime ops and match types
24 hours later: I’ll just go with macro

Out of curiosity, is your course still relevant for Scala 3? Feeling I miss crucial theoretical background.

Neither Scala 2 nor Scala 3 is a fully dependently typed language. So learning dependent types is orthogonal to the choice Scala 2/Scala 3. But the course practice was using ProvingGround based on Shapeless 2 existing only in Scala 2.

Currently the practice is not working. I was using paid features at Stepik (external grader) and although they are an international platform, they were using Russian billing systems. Since Russia war in Ukraine started, my Ukrainian bank stopped to allow transactions to them.

1 Like

Really sorry to hear about that, @DmytroMitin. Although practice isn’t working, the lectures look very promising. Hope to see another course from you someday.

1 Like