Как использовать Either внутри для понимания?

У меня есть для понимания, как это:

for {
      (value1: String, value2: String, value3: String) <- getConfigs(args)
      // more stuff using those values
}

getConfigs возвращает Either[Throwable, (Seq[String], String, String)] и когда я пытаюсь скомпилировать, я получаю эту ошибку:

value withFilter is not a member of Either[Throwable,(Seq[String], String, String)]

Как я могу использовать этот метод (который возвращает Eitherв) для понимания?

4 ответа

Как это:

for {
   tuple <- getConfigs()
} println(tuple)

Шутки в сторону, я думаю, что это интересный вопрос, но его немного называют неправильно. Проблема (см. Выше) не в том, что для понимания невозможно, а в том, что сопоставление с образцом внутри для понимания невозможно Either,

Есть документация о том, как переводы понимаются, но они не охватывают каждый случай. Насколько я вижу, этот там не освещен. Так что я посмотрел это в моем экземпляре "Программирование в Scala" - Второе издание (потому что это то, что я имею рядом с мертвыми деревьями).

Раздел 23.4 - Перевод выражений

Существует подраздел "Перевод шаблонов в генераторах", в чем и заключается проблема, как описано выше. В нем перечислены два случая:

Случай первый: кортежи

Это именно наш случай:

for ((x1, …, xn) <- expr1) yield expr2

следует перевести на expr1.map { case (x1, …, xn) => expr2), Это именно то, что делает IntelliJ, когда вы выбираете код и выполняете действие "Desugar для понимания". Ура!... но это делает его еще более странным в моих глазах, потому что код desugared фактически работает без проблем.

Так что этот случай (imho) соответствует случаю, но не соответствует тому, что происходит. По крайней мере, не то, что мы наблюдали. Хм?!

Случай два: Произвольные модели

for (pat <- expr1) yield expr2

переводит на

expr1 withFilter {
  case pat => true
  case _ => false
} map {
  case pat => expr2
}

где сейчас withFilter метод! Этот случай полностью объясняет сообщение об ошибке и почему сопоставление с образцом в Either это невозможно.

В конце концов, глава ссылается на спецификацию языка scala (хотя и на более старую), на которой я сейчас остановлюсь.

Так что извините, я не могу полностью ответить на этот вопрос, но, надеюсь, я мог бы намекнуть достаточно, в чем здесь корень проблемы.

Интуиция

Так почему же Either проблематично и не предлагает withFilter метод, где Try а также Option делать? Так как filter удаляет элементы из "контейнера" и, возможно, "все", поэтому нам нужно нечто, представляющее "пустой контейнер".

Это легко для Optionгде это очевидно None, Также легко, например, List, Не так просто для Tryпотому что есть несколько Failureкаждый может содержать конкретное исключение. Однако есть несколько сбоев, занимающих это место:

  • NoSuchElementException а также
  • UnsupportedOperationException

и вот почему Try[X] работает, но Either[Throwable, X] не. Это почти то же самое, но не совсем. Try знает что Left являются Throwable и авторы библиотеки могут воспользоваться этим.

Однако на Either (который теперь прямо смещен) "пустой" случай Left дело; который является общим. Таким образом, пользователь определяет, какой это тип, поэтому авторы библиотеки не могли выбрать общие экземпляры для каждого возможного оставленного.

Я думаю поэтому Either не обеспечивает withFilter из коробки и почему ваше выражение не удается.

Btw.

expr1.map { case (x1, …, xn)  => expr2) }

дело работает, потому что это бросает MatchError на стеке вызовов и паникует из проблемы, которая… сама по себе может быть более серьезной проблемой.

Да, и для тех, кто достаточно смел: я не использовал слово "Монада" до сих пор, потому что у Scala нет структуры данных для него, но для понимания работает только без него. Но, может быть, ссылка не повредит: аддитивные монады имеют это "нулевое" значение, что и является Either здесь отсутствует и то, что я пытался придать определенному смыслу в части "интуиция".

Вы можете сделать это, используя плагин лучше-monadic-for Олега:

build.sbt:

addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.4")

А потом:

object Test {
  def getConfigs: Either[Throwable, (String, String, String)] = Right(("a", "b", "c"))

  def main(args: Array[String]): Unit = {
    val res = for {
      (fst, snd, third) <- getConfigs
    } yield fst

    res.foreach(println)
  }
}

Урожайность:

a

Это работает, потому что плагин удаляет ненужные withFilter а также unchecked в то время как desugaring и использует .map вызов. Таким образом, мы получаем:

val res: Either[Throwable, String] = 
  getConfigs
    .map[String](((x$1: (String, String, String)) => x$1 match {
      case (_1: String, _2: String, _3: String)
    (String, String, String)((fst @ _), (snd @ _), (third @ _)) => fst
})); 

Я думаю, что вас может удивить то, что компилятор Scala выдает эту ошибку, потому что вы деконструируете кортеж на месте. Это на удивление заставляет компилятор проверять наличие withFilter метод, потому что он выглядит для компиляторов как неявная проверка типа значения внутри контейнера, а проверки значений реализуются с использованием withFilter, Если вы напишите свой код как

  for {
    tmp <- getConfigs(args)
    (value1: Seq[String], value2: String, value3: String) = tmp
    // more stuff using those values
  } 

он должен компилироваться без ошибок.

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

for {
  (value1, value2, value3) <- getConfigs(args).right.toOption
  // more stuff using those values
}

Sidenote: Я не знаю, каков твой случай использования, но scala.util.Try лучше всего подходит для случаев, когда у вас есть результат или неудача (исключение).
Просто пиши Try { /*some code that may throw an exception*/ } и вы либо будете иметь Success(/*the result*/) или Failure(/*the caught exception*/),
Если твой getConfigs метод возвращает Try вместо EitherТогда ваш вышеуказанный мог бы работать без каких-либо изменений.

Другие вопросы по тегам