Образец соответствия по значению Либо внутри для понимания? - PullRequest
0 голосов
/ 03 января 2019

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

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 ]

0 голосов
/ 03 января 2019

Вы можете сделать это, используя плагин компилятора Олега better-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 во время отладки и использует вызов .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
})); 
0 голосов
/ 03 января 2019

Как это:

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 в стек вызовов и паникует из проблемы, которая… сама по себе может быть более серьезной проблемой.

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

0 голосов
/ 03 января 2019

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

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

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

0 голосов
/ 03 января 2019

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

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, то вышеприведенный код мог бы работать без каких-либо изменений.

...