You probably thought of this idea as well, but what about something like this:
class StringOrIntOrDouble private(val first: Option[String], val second: Option[Int], val third: Option[Double]) {
def this(first: String) = this(Some(first), None, None)
def this(second: Int) = this(None, Some(second), None)
def this(third: Double) = this(None, None, Some(third))
def this(first: String, second: Int) = this(Some(first), Some(second), None)
def this(second: Int, third: Double) = this(None, Some(second), Some(third))
def this(first: String, third: Double) = this(Some(first), None, Some(third))
def this(first: String, second: Int, third: Double) = this(Some(first), Some(second), Some(third))
}
The main constructor is private
so you can't create an empty instance. Obviously you could extend it with implementation of Product
and equals
, hashCode
, toString
and other useful things as case classes do (but beware of copy
that might easily break the invariant).
Unfortunately, if you want a generic version (or you have the same type in two places) you have to move all the "subset" constructors to a named methods to a companion object or it would not compile because of type erasure.
class TripleIor[+A, +B, +C] private(val first: Option[A], val second: Option[B], val third: Option[C]) {
}
object TripleIor {
def first[A](first: A) = new TripleIor[A, Nothing, Nothing](Some(first), None, None)
def second[B](second: B) = new TripleIor[Nothing, B, Nothing](None, Some(second), None)
def third[C](third: C) = new TripleIor[Nothing, Nothing, C](None, None, Some(third))
def firstAndSecond[A, B](first: A, second: B) = new TripleIor[A, B, Nothing](Some(first), Some(second), None)
def secondAndThird[B, C](second: B, third: C) = new TripleIor[Nothing, B, C](None, Some(second), Some(third))
def firstAndThird[A, C](first: A, third: C) = new TripleIor[A, Nothing, C](Some(first), None, Some(third))
def apply[A, B, C](first: A, second: B, third: C) = new TripleIor[A, B, C](Some(first), Some(second), Some(third))
}
A more radical way would be to implement the same idea as a sealed trait
and 7 subclasses but I don't think they would be much easier to use. Also it will let you to implement copy
in a type-safe way but a the cost of a lot of typing (not shown here).
sealed trait TripleIor[A, B, C] extends Product {
def firstOption: Option[A]
def secondOption: Option[B]
def thirdOption: Option[C]
}
object TripleIor {
final case class First[A](first: A) extends TripleIor[A, Nothing, Nothing] {
override def firstOption: Option[A] = Some(first)
override def secondOption: Option[Nothing] = None
override def thirdOption: Option[Nothing] = None
}
final case class Second[B](second: B) extends TripleIor[Nothing, B, Nothing] {
override def firstOption: Option[Nothing] = None
override def secondOption: Option[B] = Some(second)
override def thirdOption: Option[Nothing] = None
}
final case class Third[C](third: C) extends TripleIor[Nothing, Nothing, C] {
override def firstOption: Option[Nothing] = None
override def secondOption: Option[Nothing] = None
override def thirdOption: Option[C] = Some(third)
}
final case class FirstSecond[A, B](first: A, second: B) extends TripleIor[A, B, Nothing] {
override def firstOption: Option[A] = Some(first)
override def secondOption: Option[B] = Some(second)
override def thirdOption: Option[Nothing] = None
}
final case class SecondThird[B, C](second: B, third: C) extends TripleIor[Nothing, B, C] {
override def firstOption: Option[Nothing] = None
override def secondOption: Option[B] = Some(second)
override def thirdOption: Option[C] = Some(third)
}
final case class FirstThird[A, C](first: A, third: C) extends TripleIor[A, Nothing, C] {
override def firstOption: Option[A] = Some(first)
override def secondOption: Option[Nothing] = None
override def thirdOption: Option[C] = Some(third)
}
final case class All[A, B, C](first: A, second: B, third: C) extends TripleIor[A, B, C] {
override def firstOption: Option[A] = Some(first)
override def secondOption: Option[B] = Some(second)
override def thirdOption: Option[C] = Some(third)
}
}
P.S. Beware that all examples here are just sketches to illustrate the idea that don't implement many useful or even required things