Abstract type projections for singleton types

I understand the reasoning for dropping abstract type projections from the language. But a type projection from a Singleton subtype is equivalent to a PDT, is it not? At least it precludes the shenningans with abstract type projections I know of that made them unsound. If they are, or at least they are a sound, then could they make a return?

The reason I am so attached to them, and a use case still possible with this constraint, is a pattern I call a ‘type dictionary’: given a large class family used to represent structural data as object trees/graphs, such as one defined by an XML schema for interchanged data, implement type safe variations of the family. This often comes by in case of various standard REST/XML protocols which allow for custom extensions made by vendors. Typically, a standard defines numerous structural types, of which ‘‘some’’ can be expanded by implementors with additional fields, or at least only ‘‘some’’ of the data types have a vendor-specific version for any vendor. For example, lets assume that a standard translates to:

case class Root(a :A, b :B, c :C)
case class A(aa :AA, ab :AB, ac :AC)
case class AA(aaa :AAA, aab: AAB, aac :AAC)
...

And that a vendor extends AA by adding an additional field:

class Vendor1AA(aaa :AAA, aab :AAB, aac :AAC, val gizmo :Gizmo) 
    extends AA(aaa, aab, aac)

Now imagine that there are multiple vendors, all with their own extensions. How to implement a service which can handle all of them (including vendor-specific extensions)?
There are two obvious approaches:

  1. The standard solution - each vendor defines their own subclass of those of standard classes for which they define extensions; handling code is aware of them and casts down those classes to the expected vendor-specific subclasses. Least work, but not type safe:
class Vendor1AAA(...) extends AAA
class Vendor2AA(...) extends AA
class Vendor3AB(...) extends AB

  1. A class family for the standard, and a homomorphic set of classes for every vendor, extending the standard classes - very far from ideal, but type safe:
class Vendor1A(...) extends A
class Vendor1AA(...) extends AA
class Vendor1AAA(...) extends AAA
...
class Vendor2A(...) extends A
class Vendor2AA(...) extends AA
class Vendor2AAA(...) extends AAA

But we can do it better with a type dictionary!

trait Protocol {
    type A <: Protocol.A[this.type]
    type AA <: Protocol.AA[this.type]
    type AAA <: Protocol.AAA[this.type]
}
object Protocol {
    type Proto = Protocol with Singleton
    case class Root[P <: Proto](a :P#A, b :P#B, c :P#C)
    case class A[P <: Proto](aa :P#AA, ab :P#AB, ac :P#AC)
    case class AA[P <: Proto](aaa :P#AAA, aab: P#AAB, aac :P#AAC)
    ...
}

Some redundancy will remain due to repeated definitions of member types for various vendor Protocol subtypes, but it are either unavoidable if the extensions are extensive, or can be minimized by a shared BaseProtocol providing concrete definitions for those types which are not extended by any vendor. In any case, defining type aliases is much more preferable to defining clones of the same classes time and again.

While the above solution can be translated to PDTs, that requires adding a public member of type P <: Proto to all the classes, and when I last used this pattern in a real world application, p1.A =:= p2.A did not hold for p1, p2 :P; P <: Protocol with Singleton, although I expect it is no longer the case with Scala 3 (and probably at least Scala 2.14 and above with its literal types).

Your use of type projection is fine and should ideally be supported in Scala 3 – the theory is well-understood by now, and there is no good reason for it not to be. You are welcome to add your voice to Make type projections safe instead of dropping them · Issue #14 · lampepfl/dotty-feature-requests · GitHub

As for the <: Singleton upper bounds, it’s currently more of an ugly hack to direct type inference, but nothing more. “Singleton-ness” cannot be expressed as a bound like that.

1 Like

I think we need to get rid first of the Singleton hack, which is indeed unsound. That could also lead to a solution to the projection issue. We plan to do that sometimes in the 3.x series, but as far as I know nobody has started working on it yet.

1 Like

“Singleton hack”? Does it mean that there will not be type Singleton in the future?