by Stephen Compall on Sep 05, 2017
Consider these two signatures.
def mungeIDs(uids: List[String], gids: List[String], oids: List[String]): Magic[String, String, String] def mungeIDsSafely[UID <: String, GID <: String, OID <: String] (uids: List[UID], gids: List[GID], oids: List[OID]): Magic[UID, GID, OID]
The second function is a strictly more general interface; the first,
concrete signature can be implemented by calling the second function,
[String, String, String] as the type arguments. There is no
need to even have the first signature; anywhere in your program where
you pass three
List[String]s as arguments to
proper type arguments will be inferred.
Yet, assuming you don’t wish
mungeIDs to be oracular (i.e. a source
of UIDs, GIDs, and OIDs), the second signature is probably much more
reliable, because type parameters are quite as
as the opaque abstract type members of the newtype mechanism.
mungeIDsSafelycan’t invent new IDs, not even with
List[String]. However, it cannot convert any
Stringback into an ID; any UIDs, GIDs, or OIDs that appear in the result
Magic[UID, GID, OID]must have come from one of the argument lists, directly. (That’s not to say that
mungeIDsSafelycan’t use the string-nature to make that decision; for example, it could always choose the smallest-as-string UID to put into the resulting
Magic. But, that UID is still enforced to be a proper element of the
uidsargument, and cannot be gotten from anywhere else.
It is entirely irrelevant that you cannot subclass
String in Scala,
Java, or whatever.
There are more types than classes.
Given the advantages, it’s very unfortunate that the signature of
mungeIDsSafely is so much noisier than that of
mungeIDs. At least
you have the small consolation of eliminating more useless unit tests.
This is a good first approximation at moving away from the dangers of
concreteness in Scala, and has the advantage of working in Java, too
(sort of; the
null prohibition is sadly relaxed).
In Scala, you can also use implicits to devise arbitrary constraints, similar to typeclasses in Haskell, and sign your functions using implicits instead, for much finer-grained control, improved safety, and types-as-documentation.
// a typeclass for "IDish types" (imagine instances) sealed trait IDish[A] def mungeIDsTCey[UID: IDish, GID: IDish, OID: IDish] (uids: List[UID], gids: List[GID], oids: List[OID]): Magic[UID, GID, OID]
Though all three types have the same constraint,
IDish, they are
still distinct types. And now, the coupling with
String is broken;
as the program author, you get to decide whether you want that or not.
Luckily, Java doesn’t make the mistake of “reified generics”. If it
did, you could ask whether
UID = GID = OID = String, and all your
safety guarantees would be gone. Forcing all generics to be reified
does not grant you any new expressive power; all it does is
permanently close off large swaths of the spectrum of mystery to you,
forbidding you from using the full scope of the design space to
improve the reliability of your well-typed programs.
The same goes for claiming that
null ought to be a default member of
every type, even the abstract ones that ought to be a little more
mysterious; it’s easy to add new capabilities (e.g. Scala’s
constraint, if you really must use
null), but taking them away is
much, much harder.
Furthering this spirit of making good programs easier to write and bad
programs harder to write, a useful area of research in Scala might be
making signatures such as that of
mungeIDsSafely nicer, or
signatures such as that of