I am modeling units of time through case classes. OOP Hierarchy
sealed abstract class Time
├── case class Second(Double)
└── case class Minute(Double)
I have a few requirements:
1) Arithmetic operations must always return the type of the left-hand-side operand; a Second + T is always a Second, and vice versa.
val a = Second(5)
val b = Minute(3)
a + b // Second(185.0)
b + a // Minute(3.0833)
1.1) This should also apply to scenarios when an operand’s type is only known vaguely as Time. A Time + T will evaluate as a vague Time type also, though the runtime value
will be a specific concrete subclass of course. While a T + Time will evaluate as a T specificially.
val general: Time = Second(10)
val specific = Second(5)
general + specific // Second(15): Time
specific + general // Second(15): Second
2) Comparison operations must work for all permutations of types
val a = Second(5)
val b = Minute(5)
a < b // true
b > a // true
2.1) including scenarios of vague Time operands
val general: Time = Second(5)
val specific = Minute(10)
general < specific // true
specific > general // true
general <= general // true
3) Standard library methods like .sum and .max must work for sequences
The known type will evaluate as the T in Seq[T], and the runtime value type will be the type of the first element.
Seq(Second(5), Second(3)).sum // Second(8): Second
Seq(Second(5), Minute(3)).sum // Second(185.0): Second | Minute
Seq(Minute(5), Second(3)).sum // Minute(3.0833): Second | Minute
Seq[Time](Second(5), Second(3)).sum // Second(8): Time
Seq(Second(5), Minute(1)).max // Minute(1): Second | Minute
I would prefer not having to implement these methods myself if possible. Is there a more automatic technique to get stdlib methods willing to operate on my own quantity types?
I also plan to support more types, Hour, Millisecond, Microsecond and so on, with operations between all of them supported. So, generalized and DRY solutions are important to avoid an unfeasibly large number of permutations.
Thank you.
Scastie: Scastie - An interactive playground for Scala.
(But you may diverge if you think there are ways my current implementation can be improved)