Pattern match on value of Either inside a for comprehension?

[亡魂溺海] 提交于 2020-01-21 11:04:09

问题


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?


回答1:


Like this:

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

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:

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

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

for (pat <- expr1) yield expr2

translates to

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

where there is now an withFilter method! This case totally explains the error message and why pattern matching in an Either 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 an withFilter method, where Try and Option do? Because filter 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 obviously None. Also easy for e.g. List. Not so easy for Try, because there are multiple Failure, each one can hold a specific exception. However there are multiple failures taking this place:

  • NoSuchElementException and
  • UnsupportedOperationException

and which is why Try[X] runs, but an Either[Throwable, X] does not. It's almost the same thing, but not entirely. Try knows that Left are Throwable and the library authors can take advantage out of it.

However on an Either (which is now right biased) the "empty" case is the Left 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 an withFilter out-of-the-box and why your expression fails.

Btw. the

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

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.




回答2:


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:

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

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 have Success(/*the result*/) or a Failure(/*the caught exception*/).
If your getConfigs method returns a Try instead of Either, then your above could would work without any changes.




回答3:


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 using withFilter. If you write your code as

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

it should compile without errors.




回答4:


You can do this using Oleg's better-monadic-for compiler plugin:

build.sbt:

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

And then:

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)
  }
}

Yields:

a

This works because the plugin removes the unnecessary withFilter and unchecked while desugaring and uses a .map call. Thus, we get:

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
})); 


来源:https://stackoverflow.com/questions/54014563/pattern-match-on-value-of-either-inside-a-for-comprehension

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!