I have a for comprehension like this:
for {
(value1: String, value2: String, value3: String) <- getConfigs(args)
// more stuff using those values
}
getConfigs
returns an Either[Throwable, (Seq[String], String, String)]
and when I try to compile I get this error:
value withFilter is not a member of Either[Throwable,(Seq[String], String, String)]
How can I use this method (that returns an Either
) in the for comprehension?
Like this:
Joking aside, I think that is an interesting question but it is misnamed a bit. The problem (see above) is not that for comprehensions are not possible but that pattern matching inside the for comprehension is not possible within
Either
.There is documentation how for comprehensions are translated but they don't cover each case. This one is not covered there, as far as I can see. So I looked it up in my instance of "Programming in Scala" -- Second Edition (because that is the one I have by my side on dead trees).
Section 23.4 - Translation of for-expressions
There is a subchapter "Translating patterns in generators", which is what is the problem here, as described above. It lists two cases:
Case One: Tuples
Is exactly our case:
should translate to
expr1.map { case (x1, …, xn) => expr2)
. Which is exactly what IntelliJ does, when you select the code and do an "Desugar for comprehension" action. Yay! … but that makes it even weirder in my eyes, because the desugared code actually runs without problems.So this case is the one which is (imho) matching the case, but is not what is happening. At least not what we observed. Hm?!
Case two: Arbitrary patterns
translates to
where there is now an
withFilter
method! This case totally explains the error message and why pattern matching in anEither
is not possible.The chapter ultimately refers to the scala language specification (to an older one though) which is where I stop now.
So I a sorry I can't totally answer that question, but hopefully I could hint enough what is the root of the problem here.
Intuition
So why is
Either
problematic and doesn't propose anwithFilter
method, whereTry
andOption
do? Becausefilter
removes elements from the "container" and probably "all", so we need something that is representing an "empty container".That is easy for
Option
, where this is obviouslyNone
. Also easy for e.g.List
. Not so easy forTry
, because there are multipleFailure
, each one can hold a specific exception. However there are multiple failures taking this place:NoSuchElementException
andUnsupportedOperationException
and which is why
Try[X]
runs, but anEither[Throwable, X]
does not. It's almost the same thing, but not entirely.Try
knows thatLeft
areThrowable
and the library authors can take advantage out of it.However on an
Either
(which is now right biased) the "empty" case is theLeft
case; which is generic. So the user determines which type it is, so the library authors couldn't pick generic instances for each possible left.I think this is why
Either
doesn't provide anwithFilter
out-of-the-box and why your expression fails.Btw. the
case works, because it throws an
MatchError
on the calling stack and panics out of the problem which… in itself might be a greater problem.Oh and for the ones that are brave enough: I didn't use the "Monad" word up until now, because Scala doesn't have a datastructure for it, but for-comprehensions work just without it. But maybe a reference won't hurt: Additive Monads have this "zero" value, which is exactly what
Either
misses here and what I tried to give some meaning in the "intuition" part.I think the part you may find surprising is that the Scala compiler emits this error because you deconstruct the tuple in place. This is surprisingly forces the compiler to check for
withFilter
method because it looks to the compilers like an implicit check for the type of the value inside the container and checks on values are implemented usingwithFilter
. If you write your code asit should compile without errors.
I guess you want your loop to run only if the value is a Right. If it is a Left, it should not run. This can be achieved really easy:
Sidenote: I don't know whats your exact use case, but
scala.util.Try
is better suited for cases where you either have a result or a failure (an exception).Just write
Try { /*some code that may throw an exception*/ }
and you'll either haveSuccess(/*the result*/)
or aFailure(/*the caught exception*/)
.If your
getConfigs
method returns aTry
instead ofEither
, then your above could would work without any changes.You can do this using Oleg's better-monadic-for compiler plugin:
build.sbt
:And then:
Yields:
This works because the plugin removes the unnecessary
withFilter
andunchecked
while desugaring and uses a.map
call. Thus, we get: