Почему создание функции карты, чей параметр имеет тип `Nothing => U`, работает?

Я пишу код Scala, который использует API, где вызовы API могут либо успешно завершиться, либо завершиться неудачей, либо вернуть исключение. Я пытаюсь сделать ApiCallResult Монада представляет это, и я пытаюсь использовать тип Nothing, чтобы случаи сбоя и исключения могли рассматриваться как подтип любого ApiCallResult тип, похожий на None или Nil. То, что у меня есть, похоже, работает, но мое использование Ничего в map а также flatMap функции меня смущают. Вот упрощенный пример того, что у меня есть только с map реализация:

  sealed trait ApiCallResult[+T] {
    def map[U]( f: T => U ): ApiCallResult[U]
  }

  case class ResponseException(exception: APICallExceptionReturn) extends ApiCallResult[Nothing] {
    override def map[U]( f: Nothing => U ) = this
  }

  case object ResponseFailure extends ApiCallResult[Nothing] {
    override def map[U]( f: Nothing => U ) = ResponseFailure
  }

  case class ResponseSuccess[T](payload: T) extends ApiCallResult[T] {
    override def map[U]( f: T => U ) = ResponseSuccess( f(payload) )
  }

  val s: ApiCallResult[String] = ResponseSuccess("foo")
  s.map( _.size )  // evaluates to ResponseSuccess(3)

  val t: ApiCallResult[String] = ResponseFailure
  t.map( _.size )  // evaluates to ResponseFailure

Так что, похоже, работает так, как я намеревался map работая на успешных результатах, но передавая ошибки и исключения вместе без изменений. Однако использование Nothing в качестве типа входного параметра не имеет смысла для меня, поскольку нет экземпляра типа Nothing. _.size функция в примере имеет тип String => Intкак это можно безопасно передать тому, что ожидает Nothing => U? Что на самом деле здесь происходит?

Я также заметил, что стандартная библиотека Scala избегает этой проблемы при реализации None, позволяя ей наследовать map функция из варианта. Это только усиливает моё ощущение, что я что-то делаю ужасно неправильно.

2 ответа

Решение

Три вещи объединяются, чтобы это произошло, все они имеют отношение к ковариации и контравариантности в лице типа дна:

  1. Nothing является нижним типом для всех типов, например, каждый тип является его супер.
  2. Тип подписи Function1[-T, +R]Это означает, что он принимает любой тип, который является супер T и возвращает любой тип, для которого R это супер.
  3. Тип ApiCallResult[+R] означает любой тип U для которого R супер из U является действительным.

Так что любой тип super из Nothing означает, что любой тип аргумента является действительным и тот факт, что вы возвращаете что-то типизированное Nothing является допустимым типом возврата.

Я полагаю, что вам не нужно различать сбои и исключения в большинстве случаев.

type ApiCallResult[+T] = Try[T]
case class ApiFailure() extends Throwable

val s: ApiCallResult[String] = Success("this is a string")

s.map(_.size)

val t: ApiCallResult[String] = Failure(new ApiFailure)
t.map(_.size)

Чтобы выявить ошибку, используйте match чтобы выбрать результат:

t match {
case Success(s) =>
case Failure(af: ApiFailure) =>
case Failure(x) =>
}
Другие вопросы по тегам