As a business user, not a library one, this is my use case, and the one for which I thought opaque type will help:
- all my business objects are identified by UUID (ie a string)
- I have (tens? hundreds?) millions of these ids in hundreds of different business objects.
- they are used in a lots of
Map
, Set
, etc
- they all have a trivial “debug/display string” (the underlying string - note that I would LOVE to be able to compile-time forbid
toString
on them) and a trivial constructor (from the string).
My primary goal is to have a compiler that helps and so I never ever want to have to wonder if I mixed a “GroupId” with a “RuleId”, be able to safely refactor, etc. For exemple I want to be able to test for equality on them and know when I don’t compare same kind of IDs (it’s always an error, likely due because I’m refactoring and I changed a method parameter or whatever).
That help MUST be as easy to use and natural in scala code as possible, BUT explicit. I never ever want a string to be magically used as a RuleId for ex. Like I never ever want to print a RuleId directly, always its debug string.
Just to be very very clear: defining such objects must be as easy and boilerplate free as possible, else they are not used as much as they should. This is integral part of the first goal. Typically, I often define algorithm-local identifier to differentiate between different step of the computation. [EDIT: instanciating these objects must be trivial, ie case class-like and no more, but defining them for the simple case must be simple too]
So for that, until now, I used case class
for that. And in some cases value class but the constraint rarely worth it (top level definition, etc)
final case class RuleId(value: String)
A second goal is to minimize as much as possible the runtime cost of my model that is only here to help developpers at compile time. The fact that a ruleid is more than a string is useless at runtime. Actually, I would like to be able to explore alternative runtime representation without changing compile-time API (which of course includes whatever serialization/debug representation needed). For ex: I would love to know if switching from a string encapsulated in class is much more costly than, say, array of two longs in real load. But today the cost of that test is big.
Finally, some IDs are composed with 2 or more UUIDs and I have a lot of others similare objects like that, composed from some simple types, like stats objects (tens of long, each long having its own semantic). And I want to have a consistant recipe for dealing with all these cases in a consistant way, so that new dev on the project (or old dev coming to a similar problem a couple of month after) can just copy/past existing example without having to worry of too much ceremony, scalac implementation details, or other surprising things.
For these three points, I would LOVE to have a consistent, boiler-plate light way to deal with that case (without having to wait for Valhalla)
I will explore if opaque type are a good feet for that.