Search code examples
scalashapelesstype-safety

Shapeless type disjunction for more then 2 types


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())

Solution

  • 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
    

    How to define "type disjunction" (union types)?