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