How to provide a type disjunction for 3 or more types in shapeless? Example:
import shapeless._
object Tst extends App {
sealed trait Base
final case class A() extends Base
final case class B() extends Base
final case class C() extends Base
final case class D() extends Base
def AorB[T: (A |∨| B)#λ](t: T): Unit =
t match {
case _: A => println("A")
case _: B => println("B")
}
AorB(A()) //Ok
AorB(B()) //Ok
def AorBorC[T: (A |∨| B |∨| C)#λ](t: T): Unit =
t match {
case _: A => println("A")
case _: B => println("B")
case _: C => println("C")
}
AorBorC(A()) //compile-error
AorBorC(B()) //compile-error
AorBorC(C()) //Ok
}
As can be seen for disjunction of 2 types it works completely fine. But for disjunction of 3 types it does not work as expected.
Compile errors are:
Error:(28, 10) Cannot prove that (Tst.A => Nothing) => Nothing <:< Object{type λ[X] = (X => Nothing) => Nothing <:< Tst.A => Nothing with Tst.B => Nothing => Nothing} => Nothing with Tst.C => Nothing => Nothing.
AorBorC(A())
and
Error:(29, 10) Cannot prove that (Tst.B => Nothing) => Nothing <:< Object{type λ[X] = (X => Nothing) => Nothing <:< Tst.A => Nothing with Tst.B => Nothing => Nothing} => Nothing with Tst.C => Nothing => Nothing.
AorBorC(B())
shapeless.|∨|
doesn't work for more than 2 types.
http://milessabin.com/blog/2011/06/09/scala-union-types-curry-howard/
For more than 2 types encoding becomes more complicated.
One encoding is for 2, 4, 8 ... types
type ¬¬¬¬[T] = ¬¬[¬¬[T]]
type |∨∨|[T, U] = {
type λ[X] = ¬¬¬¬[X] <:< (T ∨ U)
}
def AorBorC[T: ((A ∨ B) |∨∨| (C ∨ C))#λ](t: T): Unit =
t match {
case _: A => println("A")
case _: B => println("B")
case _: C => println("C")
}
AorBorC(A()) //Ok
AorBorC(B()) //Ok
AorBorC(C()) //Ok
Another is for arbitrary number of types
trait Disj[T] {
type or[S] = Disj[T with ¬[S]]
type apply = ¬[T]
}
type ∨∨∨[T1, T2, T3] = Disj[¬[T1]]#or[T2]#or[T3]#apply
type |∨∨∨|[T1, T2, T3] = {
type λ[X] = ¬¬[X] <:< ∨∨∨[T1, T2, T3]
}
def AorBorC[T: |∨∨∨|[A, B, C]#λ](t: T): Unit =
t match {
case _: A => println("A")
case _: B => println("B")
case _: C => println("C")
}
AorBorC(A()) //Ok
AorBorC(B()) //Ok
AorBorC(C()) //Ok