Can I somehow (with match types?) introduce a default type to use if a type is undefined/abstract?

76 Views Asked by At

Let's have some application specific type:

  class Carrier

I am dealing with polymorphic tuples of its subtypes. Occasionally, a Carrier would have a payload (it is use-case specific, rather than dictated by a Carrier subclass) and I'd like to collect those in a new tuple:

  class Carrier {
    type Payload[T <: Tuple] <: Tuple
  }

Then, my data would look something like this:

  (fullCarier.asInstanceOf[Carrier { type Payload[T <: Tuple] = Cargo *: T }], emptyCarrier.asInstanceOf[Carrier { type Payload[T <: Tuple] = T }])

The problem here is that 'full' carriers are a very corner case, with 95% of use cases at least dealing only with 'empty' ones. Having (SubCarrier1 { type Payload[T <: Tuple] = T }, SubCarrier2 { type Payload[T <: Tuple] = T }) instead of (SubCarrier1, SubCarrier2) gets extremely noisy very quickly - I'd rather drop that 'payload' use case completely than resort to this. Even

  type Empty[C <: Carrier] = C { type Payload[T <: Tuple] = T }
  val data :(Empty[SubCarrier1], Empty[SubCarrier2])

is too high a price to pay. I'm still new to Scala 3, but perhaps this could be defined somehow as a default case of a match type? Pseudo code:

   class Carrier { type Payload[T <: Tuple] <: Tuple; type Full :Boolean with Singleton }

   type Collect[D <: Tuple] = D match 
     case h *: t => (h match 
       case Carrier { type Full = true; type Payload[P] = payload[P] } => payload[Collect[T]]
       case Any => Collect[t]
     )
     case EmptyTuple => ()

For that, I would essentially need something like this:

  val carrier :CarrierSubclass1
  type MyType[X] = carrier.Payload[X] match {
     case payload[X] => payload[X] //carrier.Payload is a concrete type
     case _ => X //if CarrierSubclass1 does not define Payload, I want to use a default.
  }

This is somewhat fishy, as the result of matching would depend on our knowledge about the type of carrier: if it is in fact a CarrierSubclass2 <: CarrierSubclass1 and CarrierSubclass2 has a definition of Payload, then the result would be different even though we match the same object. It is however not that different to invariant higher types, where MyOtherType[CarrierSubclass1] is unrelated to MyOtherType[CarrierSubclass2]. To tell the truth, I am still confused as to how match types work.

A bigger problem of course is that D could be SubCarrier1 *: Tuple, and I still would like to be able to do something sensible with it. I don't like how Scala 3 handles tuples, as for a type like above, the full type of the tuple has to be known for all defined operations like Concat, etc., to be available. On the other hand, defining those recursively as member types inside Tuple would work and not lose any type information, even if the executed code is generic (i.e., D =:= (H *: T)). So, the result of t1.concat(tuple2) would be of type t2.AppendTo[T1], where t :T1, and anyone knowing the exact type of T2 and T1 would have full information, despite it being not known where the concatenation took place.

So, realistically, I am probably looking into dealing with my own HList-like type,

  trait HList { 
    type Payload 
    def payload :Payload
  }
  case class ::[H <: Carrier, T <: HList](h :H, t :T) extends HList {
    type Payload = h.Payload[t.Payload]
    def payload = h.payload(t.payload)
  }
  case object HNil extends HList {
    type Payload = EmptyTuple
    def payload = EmptyTuple
  }

but somehow tack on a match type instead of h.Payload.

Could something like this work?

0

There are 0 best solutions below