Преобразовать вариант в любой в Scala

Предположим, мне нужно преобразовать Option[Int] в Either[String, Int] в Scala. Я хотел бы сделать это так:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold(Left("No number")) {x => Right(x)}

К сожалению, приведенный выше код не компилируется, и мне нужно явно добавить тип Either[String, Int]:

ox.fold(Left("No number"): Either[String, Int]) { x => Right(x) }

Можно ли таким образом преобразовать Option в Either без добавления типа ?
Как бы вы предложили преобразовать Option в Either ?


person Michael    schedule 10.01.2016    source источник


Ответы (3)


Нет, если вы сделаете это таким образом, вы не сможете пропустить тип.

Предполагается, что тип Left("No number") будет Either[String, Nothing]. Только из Left("No number") компилятор не может знать, что вы хотите, чтобы второй тип Either был Int, и вывод типа не зайдет так далеко, что компилятор просмотрит весь метод и решит, что он должен быть Either[String, Int].

Вы можете сделать это разными способами. Например, при сопоставлении с образцом:

def foo(ox: Option[Int]): Either[String, Int] = ox match {
  case Some(x) => Right(x)
  case None    => Left("No number")
}

Или с выражением if:

def foo(ox: Option[Int]): Either[String, Int] =
  if (ox.isDefined) Right(ox.get) else Left("No number")

Или с Either.cond:

def foo(ox: Option[Int]): Either[String, Int] =
  Either.cond(ox.isDefined, ox.get, "No number")
person Jesper    schedule 10.01.2016
comment
ox.map(Right(_)).getOrElse(Left("No number")) тоже работает, но создает промежуточный экземпляр Option. - person knutwalker; 10.01.2016
comment
И есть еще ox.toRight("No Number"), но так как этот метод не имеет явного возвращаемого типа, он выводится как Serializable with Product with Either[String, Int] - person knutwalker; 10.01.2016
comment
@knutwalker О, спасибо. Мне нравится ваше toRight решение. Я просто добавлю тип: ox.toRight("No Number"): Either[String, Int]. - person Michael; 10.01.2016

Я не уверен, какую версию Scala вы использовали в то время. В настоящее время в Scala 2.12.6 нет проблем с компиляцией вашего кода, например:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.toRight("No number")

Еще один момент, который я хотел бы отметить, заключается в том, что свертывание (хотя это мой предпочтительный метод свертывания всего, что имеет метод fold) довольно часто требует помощи с параметрами типа. Есть два способа, которыми компилятор может проверить тип выражения: либо он может вывести параметры типа, либо просто найти их явно определенными.

В вашем примере, если вы пытаетесь свернуть такой вариант:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold(Left("No number") : Either[String, Int])(x => Right(x))

Вы явно предоставляете информацию о типе для первого аргумента, который, в свою очередь, можно использовать для вывода параметра типа fold. Вы помогаете механизму вывода типов.

С другой стороны, вы можете просто явно указать параметр типа для fold следующим образом:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold[Either[String, Int]](Left("No number"))(x => Right(x))

Теперь ваши фактические аргументы (на уровне значения) не замусорены лишней информацией о типе, и когда компилятор смотрит на них, не происходит вывода типа, он может сразу сказать, что такое параметр типа fold, поскольку он был явно предоставлен. Используйте квадратные скобки, чтобы явно указать параметр типа.

Еще один момент, касающийся x => Right(x), здесь вы практически создаете новый функциональный литерал, который ничего не делает, кроме передачи x в метод apply объекта-компаньона класса Right case. У вас уже есть функция соответствующей формы. Он принимает x и возвращает Right(x). Это метод apply. Вы можете обратиться к нему (передать его) напрямую.

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold[Either[String, Int]](Left("No number"))(Right.apply)
person Peter Perháč    schedule 20.07.2018

Причина, по которой необходима аннотация типа, заключается в том, как работает вывод типа в Scala 2 для несколько списков параметров, где

  • список параметров рассматриваются по одному, и
  • ограничения, накопленные в первых списках параметров, применяются к следующему списку параметров.

Рассмотрим подпись Option#fold

def fold[B](ifEmpty: => B)(f: A => B): B

где мы видим параметр типа B и два списка параметров. Теперь тип аргумента, предоставляемого первому списку параметров, — Left[String,Nothing], потому что

scala> Left("No number")
val res0: scala.util.Left[String,Nothing] = Left(No number)

это означает, что параметр типа B выводится как Left[String,Nothing], что, в свою очередь, ограничивает ожидаемый тип аргумента, предоставленного второму списку параметров, функцией возвращаемого типа Left.

A => Left[String,Nothing]

однако мы предоставляем функцию возвращаемого типа Right, поэтому возникают ошибки с несоответствием типа

Welcome to Scala 2.13.3 (OpenJDK 64-Bit Server VM, Java 1.8.0_252).
Type in expressions for evaluation. Or try :help.

scala> def foo(ox: Option[Int]): Either[String, Int] =
     |   ox.fold(Left("No number")) {x => Right(x)}
         ox.fold(Left("No number")) {x => Right(x)}
                                               ^
On line 2: error: type mismatch;
        found   : scala.util.Right[Nothing,Int]
        required: scala.util.Left[String,Nothing]

Обратите внимание, что Scala 3 (Dotty) содержит улучшения для вывода типов, поэтому ваш фрагмент работать из коробки без необходимости предоставлять явную аннотацию типа

Starting dotty REPL...
scala> def foo(ox: Option[Int]): Either[String, Int] =
     |   ox.fold(Left("No number")) {x => Right(x)}
     |
def foo(ox: Option[Int]): Either[String, Int]
person Mario Galic    schedule 21.07.2020
comment
Вывод типа scala 2 по нескольким спискам параметров является ключом к этому вопросу. Другие ответы пропустили это. Это заслуживает того, чтобы быть принятым ответом. - person montrivo; 29.12.2020