I have what feels like a simple example I tested for trying to be able to generate a type-class for a union type:
final case class Options[T](options: List[T]) {
def |[T2](that: Options[T2]): Options[T | T2] = Options(this.options ::: that.options)
}
object Options {
def apply[A](implicit ev: Options[A]): Options[A] = ev
inline given union[A, B](using a: Options[A], b: Options[B]): Options[A | B] = a | b
}
I tried a bunch of stuff with NotGiven, as well as trying to using macros to assert that A < B lexicographically, and that A and B are disjoint (using macros), and was having no success.
It seems to me that whenever you’d want the compiler to try calling union[A, B], it actually tries calling it with union[A | B, A | B].
Its quite likely Im just being a dummy here, and theres actually an obvious way to do this, and I just dont know what it is. If thats the case, please tell me what Im doing wrong. If Im not missing something totally obvious here, it feels like this is a bit of a compiler issue. I feel like getting an implicit instance of F[A | B] given an F[A] and F[B] should be a super simple thing to do, and that is just not my experience today.
There were some discussions on the scala discord server relating to this:
TLDR, its a tricky problem to solve for disambiguating A | B and B | A. This gets even trickier when you have to worry about
A | B | C | D
A | B | (C & D)
(A & B) | C | D
A | (B & C) | D
(A & B) | (C & D).
Would it be possible to have the compiler be able to generate a Union instance like this?
actual repr:
trait Union[AB] {
type A
type B
}
example repr:
sealed trait SimpleType
object SimpleType {
final case class Basic(name: String) extends SimpleType {
override def toString: String = name
}
final case class Or(left: SimpleType, right: SimpleType) extends SimpleType {
override def toString: String = s"($left | $right)"
}
final case class And(left: SimpleType, right: SimpleType) extends SimpleType {
override def toString: String = s"($left & $right)"
}
}
final case class UnionInstance(ab: SimpleType, a: SimpleType, b: SimpleType)
object UnionInstance {
implicit val ord: Ordering[SimpleType.Basic | SimpleType.And] =
Ordering
.by[SimpleType.Basic | SimpleType.And, Int] {
case SimpleType.Basic(_) => 1
case SimpleType.And(_, _) => 2
}
.orElseBy(_.toString)
private def flattenOrs(t: SimpleType): List[SimpleType.Basic | SimpleType.And] =
t match
case SimpleType.Or(left, right) => flattenOrs(left) ::: flattenOrs(right)
case t @ SimpleType.Basic(_) => t :: Nil
case t @ SimpleType.And(_, _) => t :: Nil
private def makeOrs(a: SimpleType, b: List[SimpleType]): SimpleType =
b match
case head :: tail => SimpleType.Or(a, makeOrs(head, tail))
case Nil => a
def make(ab: SimpleType): Option[UnionInstance] =
flattenOrs(ab).sorted match
case a :: b :: c => UnionInstance(ab, a, makeOrs(b, c)).some
case _ => None
}