Implicit error when trying to implement the `Absurd` typeclass

172 views Asked by At

I'm trying to implement the Absurd typeclass (as seen in Haskell's Data.Boring library) in Scala.

I'm able to define an Absurd instance for Nothing. Unfortunately, when I try to define an absurd instance for Either, I get a missing implicit error

sealed trait Absurd[A] {
    def absurd[X](a: A): X
}

object Absurd {
  def apply[A: Absurd, B](a: A):B = implicitly[Absurd[A]].absurd[B](a)

  implicit val absurdForNothing: Absurd[Nothing] = new Absurd[Nothing]{
    override def absurd[X](a: Nothing): X = a
  }

  implicit def absurdForEither[A: Absurd, B: Absurd]: Absurd[Either[A, B]] = new Absurd[Either[A, B]]{
    override def absurd[X](a: Either[A,B]): X = a match {
      case Left(a) => Absurd[A, X](a)
      case Right(b) => Absurd[B, X](b)
    }
  }
}

This compiles:

implicitly[Absurd[Nothing]]

This fails to compile:

implicitly[Absurd[Either[Nothing, Nothing]]]

I'm using Scala Version "2.13.2".

It is possibly intersting to note, that the following, very similar code (which doesn't involve Nothing), does compile:

trait SomeTypeclass[A]
case class SomeInstance()

object SomeTypeclass {
  implicit val someTypeclassForSomeInstance: SomeTypeclass[SomeInstance] = new SomeTypeclass[SomeInstance] {}

  implicit def someTypeclassForEither[A: SomeTypeclass, B: SomeTypeclass]: SomeTypeclass[Either[A, B]] = new SomeTypeclass[Either[A, B]] {}
}
object SomeApplicationCode {
  implicitly[SomeTypeclass[Either[SomeInstance, SomeInstance]]]
}
1

There are 1 answers

2
Joe On BEST ANSWER

Thanks to Dmytro's comment, I was able to find this post suggesting a workaround for this bug.

In short, we can define a type alias Empty.T for subtypes of Nothing

object Empty{
  type T <: Nothing
}

Since Nothing has no values, and no subtypes, Empty.T will also have no values. This lets us write our Absurd instances:

object Absurd {
  def apply[A: Absurd, B](a: A):B = implicitly[Absurd[A]].absurd[B](a)

  implicit val absurdForEmptyT: Absurd[Empty.T] = new Absurd[Empty.T]{
    override def absurd[X](a: Empty.T): X = a
  }

  implicit def absurdForEither[A:Absurd, B: Absurd]: Absurd[Either[A, B]] = new Absurd[Either[A, B]]{
    override def absurd[X](a: Either[A,B]): X = a match {
      case Left(a) => Absurd[A,X](a)
      case Right(b) => Absurd[B, X](b)
    }
  }
}

This works! The following will compile:

implicitly[Absurd[Either[Empty.T, Empty.T]]]

as does:

implicitly[Absurd[Either[Nothing, Nothing]]]

Since I'm porting Haskell code, which doesn't have to worry about variance, it would be equally valid to define our own empty type as a workaround:

sealed trait Empty

object Absurd {
  def apply[A: Absurd, B](a: A):B = implicitly[Absurd[A]].absurd[B](a)

  implicit val absurdForEmpty: Absurd[Empty] = new Absurd[Empty]{
    override def absurd[X](a: Empty): X = ???
  }
  // ...
}

This works, but personally I prefer the first approach, since it doesn't ignore the Empty type Nothing that's already built into Scala, and since It doesn't rely on us to use ??? to write the initial Absurd[Empty] instance.