I’m a little late to the discussion, but I find the syntax of the feature quite confusing.
AFAIU, the major motivation behind the feature is improving performance as explained in the original SIP document. I’m wondering though, shouldn’t the performance boost be implemented by the compiler behind the scenes and not exposed in the source code?
Consider a much less radical syntactic change:
opaque class Logarithm extends Double {
def toDouble: Double = math.exp(this)
def +(other: Logarithm): Logarithm = Logarithm(toDouble + other.toDouble)
def *(other: Logarithm): Logarithm = +(other)
}
object Logarithm {
def apply(d: Double): Logarithm = math.log(d)
def safe(d: Double): Option[Logarithm] =
if (d > 0.0) Some(apply(d)) else None
}
Or perhaps the class signature should be this:
class Logarithm opacifies Double
This syntax remains very close to existing constructs which are familiar and are less confusing. It also doesn’t expose the underlying implementation of this feature – whether it’s being done with extension methods or something else.
There’s also quite a bit of ambiguity with the proposed syntax. Let’s take the “permissions” example from the dotty documentation:
object Access {
opaque type Permissions = Int
opaque type PermissionChoice = Int
opaque type Permission <: Permissions & PermissionChoice = Int
def (x: Permissions) & (y: Permissions): Permissions = x | y
// ambiguity 1
def (x: PermissionChoice) | (y: PermissionChoice): PermissionChoice = x | y
def (x: Permissions).is(y: Permissions) = (x & y) == y
def (x: Permissions).isOneOf(y: PermissionChoice) = (x & y) != 0
val NoPermission: Permission = 0
val ReadOnly: Permission = 1
val WriteOnly: Permission = 2
// ambiguity 2
val ReadWrite: Permissions = ReadOnly | WriteOnly
val ReadOrWrite: PermissionChoice = ReadOnly | WriteOnly
}
In the first case (“ambiguity 1”), it is unclear whether the pipe in x | y
is the Int
operation or a recursive call to the same method.
In the second case, |
is an ambiguous method for ReadOnly
(of type Permission
), and that ambiguity is somehow resolved by the explicit type of the val
to which the operation is assigned. I believe that this is a precedent that does not exist with any other feature in the language.
I would’ve imagined the permissions use-case to be coded this way:
opaque class Permission extends Int {
def |(other: Permission): Permission = Permission(super | other)
def isOneOf(permission: Permission): Boolean = (this & permission) != 0
}
object Permission {
val NoPermission = Permission(0)
val WriteOnly = Permission(2)
val ReadOnly = Permission(4)
val ReadWrite = ReadOnly | WriteOnly // 6
}
It’s probably worth mentioning that I didn’t fully understand what the original example was trying to model. There is no difference between “and” and “or” when talking about combination of permissions; ReadWrite == ReadAndWrite == ReadOrWrite
.