Proposal for Opaque Type Aliases

I am not disputing the need for opaques nor do I ask for a better justification. I am times and times again trying to view them differently only so their syntax will be clearer (in my opinion, obviously).

I am by no means eliminated motivation #1 (performance), but argue that it shouldn’t affect syntax.

Yes, I am wondering what cases qualify as motivation #2. As of the Scala.js example, I didn’t infer it at all; quite the opposite, I expressed unfamiliarity with the topic and hoped for a better and more specific explanation. I indeed noted that if this example is platform-specific, then I’d question its significance in the overall design of opaques’ syntax.

That leaves us with motivation #3, which is what I’m trying to focus on – more readable code.

I am not disputing their usefulness, but rather their syntax / encoding.


Let’s get more concrete and compare between the syntax alternatives.

Opaque as a type modifier

This is the currently proposed syntax:

object Access {
  opaque type Permission = Int
  implicit class ops(p: Permission) {
    def |(other: Permission): Permission = p | other
  }
  val NoPermission: Permission = 0
}

Cons:

  1. Enclosing object with a different name is required.
  2. Need to be familiar with extensions in order to add functions.
  3. Ambiguity in method invocation (is p | other recursive?).
  4. Need to be familiar with additional scope rules / semantics.

Opaque as a type entity

This is an alternative syntax in which opaques are type entities – much like class and object are. It doesn’t mean they are classes, but that they are on-par with classes in terms of syntax.

opaque Permission(i: Int) {
  def |(other: Permission): Permission = i | other.i
}
object Permission {
  val NoPermission =  Permission(0)
}

I believe this syntax overcomes all of the cons of the previous syntax. It feels familiar as it uses the “entity” syntax where an encapsulated data type is associated with methods. It can also leverage existing access modifiers (private) to control the level of encapsulation; it doesn’t need new scope mechanisms.

Nominal types

This syntax is not an alternative to the previous two, but a complementary one. It better addresses the need to distinguish between type aliases of the same underlying type without encapsulating them.

nominal type Password = String
nominal type GUID = String

These are not intended to be extended with more functions – that’s what opaques are for. Indeed the semantics of those need to be further worked on:

import Password

val password: Password = "god123"

password + password // ok
password.toLowerCase() // what's the return type?
password + "x" // allowed? what's the return type?
2 Likes