Почему создание функции карты, чей параметр имеет тип `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 ответа
Три вещи объединяются, чтобы это произошло, все они имеют отношение к ковариации и контравариантности в лице типа дна:
Nothing
является нижним типом для всех типов, например, каждый тип является его супер.- Тип подписи
Function1[-T, +R]
Это означает, что он принимает любой тип, который является суперT
и возвращает любой тип, для которогоR
это супер. - Тип
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) =>
}