I've recently heard about unboxed tagged types in scala and while I was trying to learn how exactly it works, I've found this question that points to problems the implementation in scalaz had. One of the consequences of the fix was having to explicit unwrap the tagged type:
def bmi(mass: Double @@ Kg, height: Double @@ M): Double =
Tag.unwrap(mass) / pow(Tag.unwrap(height), 2)
Then I considered the original idea, where I could do something like:
type Tagged[U] = { type Tag = U }
type @@[T, U] = T with Tagged[U]
trait Kilogram
trait Meter
type Kg = Double @@ Kilogram
type M = Double @@ Meter
def bmi(mass: Kg, height: M): Double = mass / pow(height, 2)
So now I'm wondering whether the issues found previously in scalaz are specific to it's approach, or if the simple implementation could also have problems with erasure, arrays or varargs. The thing is I'm still learning scala, so my understanding of it's type system is quite limited and I couldn't figure it out by myself.
It is unsafe from a type safety perspective.
T @@ Uis a subtype ofTand an instance ofT @@ Umay be used where ever an instance ofTis required, even when it is accidental. Consider the followingOK, everything is cool, right? This is why
T @@ Uexists. We want to be able to make a new type and define new type classes for it. Unfortunately, everything isn't ok when your coworker comes along and performs some valid refactoring and accidentally breaks your business logic.Oops
In this case, the use of subtyping, combined with the covariance on the
List[+A]type parameter caused a bug. AList[Int @@ Max]may be substituted wherever aList[Int]is needed.