About the syntax boilerplate, maybe it could be possible to allow opaque as a modifier for a class to mean “an opaque type + the standard boilerplate”, similarly to how implicit class means “a class + an implicit def”.
For example:
opaque [implicit] class T ([private] val a: A) {
{defs...}
}
Could be syntaxic sugar for:
opaque type T = A
object T {
[implicit] def apply(a: A): T = a
implicit class T$Ops ($self: T) extends AnyVal {
inline(?) [private] def a: A = t
{defs...}
}
}
Requirements of value classes would apply equally to “opaque” classes:
- A single
val parameter
- No fields definitions, only methods
- Cannot be nested inside another class
Ideally, this inside the opaque class body would be rewritten to $self and have type T.
Adding implicit to the opaque class definition would add implicit on the apply method, meaning that the value can be implicitly wrapped, otherwise explicit wrapping is required.
Adding private to the constructor parameter would prevent the underlying value from being accessed directly and require explicit accessors to be defined.
Maybe we could even imagine opaque class T private ( ... ) that would mean that the apply method synthesized on the companion object is also private, requiring custom wrapper to be defined on the companion object (eg. to perform validation).
While this syntax is arguably more complex that the single opaque type definition, I believe it allows many common use cases of opaque type to be expressed with a lot less boilerplate. It would also be syntactically very similar to current value classes, meaning that converting code would be as easy as replacing the extends by opaque to get the unboxed semantic.
This syntax might also scale to future JVM-level value classes by allowing more than a single parameter in the class constructor, but who knows. This may not be a goal for this feature.
A last idea: if some people are not willing to introduce opaque as a keyword, maybe the inline keyword from Dotty could be used instead (an inline class is a class that disappear at runtime), it obviously work a lot better in the inline class than in the the inline type version.