I don’t think you’re mean. I hope you don’t think I come across as mean 
It’s hard to give a comprehensive list of use cases. I think the one @kai gave you was pretty telling.
It’s also possible to define a type consisting of Natural numbers:
object o:
opaque type Nat <: Int = Int
def natOption(i: Int): Option[Nat] = if i >= 0 then Some(i) else None
This exposes that Nat
is an Int
, making it possible to pass it to methods requiring Int
and to use any of the methods from Int
. Most importantly, you can’t pass any Int
to a function that wants a Nat
.
What you want, if I understand you correctly, is the ability to use all the Int
methods on a Nat
without exposing the subtyping relation so that a Nat
can’t be implicitly upcasted to an Int
.
I’ve actually been playing with an idea to make it possible to expose methods from the underlying type as methods on the opaque type. The problem with this is that it’s easier said than done to just expose all methods.
Consider the Nat
case:
What is the type of (x: Nat) + (y: Nat)
? The sum of two natural numbers are natural right? So Nat
would be a good choice. Except that we forgot to consider overflow so now 2147483647: Nat + 1: Nat = -2147483648: Nat
. Oops
How about (x: Nat) - (y: Nat)
? This could be negative. Should we expose Int
as return type? Or not expose the method at all?
It doesn’t always make sense to expose all methods unconditionally. It makes even less sense to replace the underlying type with the opaque type in all method’s type signatures. So either way, exposing methods should be an active choice, and if fancy type signatures are needed, they should be overridden explicitly.
It should also be noted that some of this is partly possible already:
-
You can use extension methods to manually provide forwarding methods for the ones you want. Sure, it’s boilerplaty, but it’s doable. All the methods on IArray
are extension methods.
-
Also, if you have a method that return this.type
then the opaque type is preserved:
scala> abstract class Box[+T](val x: T):
| def doStuff(): this.type
|
// defined class Box
scala> object o:
| opaque type MyBox[T] <: Box[T] = Box[T]
| def MyBox[T](x: Box[T]): MyBox[T] = x
|
// defined object o
scala> lazy val abox: Box[Int] = ???
lazy val abox: Box[Int]
scala> lazy val mybox = o.MyBox(abox)
lazy val mybox: o.MyBox[Int]
scala> :type mybox.doStuff()
o.MyBox[Int]
What I’m trying to say with this really long post is that what you want is absolutely doable, (with the caveat that there are a lot of edge cases to consider) and the current proposal doesn’t really need to change in any significant way to allow it.
Would it be enough if you could just write something like this:
opaque type ID = Int
transparent extension (self: ID)
export self._
?