A quite typical use case: an object (or class) declares several public vals of related types, and it would like to declare an accessor returning a collection containing all of them:
case class Ball(dia :Int)
object Balls {
val tennis = Ball(7)
val football = Ball(22)
val basketball = Ball(24)
val balls = Seq(tennis, football, basketball)
}
This example obviously violates DRY and is error prone. It can be quite easily solved using mutable state (such as adding an implicit Builder[Ball, Seq[Ball]]
parameter to the Ball
constructor. That solution however isn't without issues, either. Particularily once we try to generalize the solution and/or have a class hierarchy where every class declares some values, the moment when we should switch from mutable partial summary to final immutable value is not clear.
More as an intellectual exercise and out of curiosity I was trying to come up with a purely functional variant, without much success. The best I came up with is
object Balls {
import shapeless.{::, HNil}
val (balls @
tennis ::football::basketball::HNil
) =
Ball(7)::Ball(22)::Ball(24)::HNil
}
Which is quite neat, but becomes unmanagable once the number of balls or their initializers aren't tiny. A working alternative would be to change everything into a HMap, but I generally try to avoid shapeless dependencies in public API. It seems like it could be perhaps doable with scala continuations, but I have no idea how to make the declarations non-local to the reset block.
EDIT: What I haven't stressed before, and the reason why scala.Enumeration
doesn't do the job for me, is that in the real case the objects aren't identical, but are in fact composite structures, which constructors take several lines or more. So, while the final type may be the same (or at least I'm not interested in the details), it isn't a simple enumeration, and for readability reasons it is important that the name of the declared member/key can be easily tied visually with its definition. So my shapeless solution here, as well as the shapelessless Seq-based proposed, are very susceptible to off-by-one errors, where a modification is made to the wrong value, by mistaking its real identifier.
Of course, the real case is currently implemented similarly to scala.Enumeration, by maintaining a sequence of values produced by the inherited constructor method. It suffers however from all problems that Enumeration does, and amplifies the probability of errors from calling the constructor outside of actual object Balls
initializer, or discarding a value in a conditional block. Besides, I was quite interested how this is solved in purely functional languages.
Any ideas how to have a cake and eat it?