Неявная ошибка при попытке реализовать класс типов `Absurd` - PullRequest
4 голосов
/ 25 мая 2020

Я пытаюсь реализовать класс типов Absurd (как показано в библиотеке Data.Boring Haskell) в Scala.

Я могу определить экземпляр Absurd для Nothing. К сожалению, когда я пытаюсь определить абсурдный экземпляр для Either, я получаю недостающую неявную ошибку

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

Это компилируется:

implicitly[Absurd[Nothing]]

Это не компилируется:

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

Я использую Scala версию «2.13.2».

Возможно, интересно отметить, что следующий очень похожий код (который не включает Nothing) , компилируется:

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 Ответ

1 голос
/ 25 мая 2020

Благодаря комментарию Дмитрия мне удалось найти этот пост, предлагающий обходной путь для этой ошибки .

Короче говоря, мы можем определить псевдоним типа Empty.T для подтипов Nothing

object Empty{
  type T <: Nothing
}

Поскольку Nothing не имеет значений и подтипов, Empty.T также будет не имеют ценностей. Это позволяет нам писать наши экземпляры Absurd:

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

Это работает! Следующее будет компилироваться:

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

как и:

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

Поскольку я портирую код Haskell, который не должен беспокоиться о дисперсии, он в равной степени можно было бы определить наш собственный пустой тип в качестве обходного пути:

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 = ???
  }
  // ...
}

Это работает, но лично я предпочитаю первый подход, поскольку он не игнорирует пустой тип Nothing, который уже встроен в Scala, и поскольку он не требует от нас использования ??? для записи начального экземпляра Absurd[Empty].

...