Using guards in for comprehension

Given the following snippet:

for {
  a <- getSomeList()
  _ <- doSomethingWithAList(a)
} yield a

doSomethingWithAList(List[-]) can only be invoked for non-empty lists.

I thought I remembered that we could use if guards in for comprehensions and tried the following:

for {
  a <- getSomeList()
  _ <- if (a.nonEmpty) doSomethingWithAList(a)
} yield a

But this does not work.

However, this does:

for {
  a <- getSomeList()
  _ <- if (a.nonEmpty) doSomethingWithAList(a) else Right(None)
} yield a

But this does not look right.

Is there a way to achieve what I need here? Or how would I solve this?

https://docs.scala-lang.org/tour/pattern-matching.html shows examples of the guard syntax

Yeah, it won’t work because Either does not have a filter (actually withFilter) method.
And this is because there is no meaningful way to filter any Either, but you can easily provide some error value wrapped on a Left when you filter that value.

So you can do something like this:

_ <- if (a.nonEmpty) doSomethingWithAList(a) else Left("error")

(replace "error" with an appropriated value)
That way the computation will be halted.

However, a better design is to avoid impossible states altogether, something like this using cats.

import cats.data.NonEmptyList

type Error = String
type ErrorOr[+A] = Either[Error, A]

defgetSomeList(): ErrorOr[List[Foo]]
def doSomethingWithAList(list: NonEmptyList[Foo]): ErrorOr[Unit]

for {
  a <- getSomeList()
  aNonEmpty <- NonEmptyList.fromList(a).toRight(left = "a was empty")
  _ <- doSomethingWithAList(aNonEmpty)
} yield a
1 Like

Thank you for your reply! I still try to find the time to look into cats. It’s mentioned everywhere and so I believe I could benefit from it, too. But for now I have to stick to plane Scala.

I suppose this works the same way if I want to return a Right what so ever? Both, empty and non-empty, are valid states, I just cannot modify the doSomethingWithAList and therefore I cannot put an if there.

Not sure what you mean.
So NonEmptyList.fromList returns an Option, and I am transforming that Option into an Either by providing my own left value.
That is the same as

_ <- if (a.nonEmpty) doSomethingWithAList(a) else Left("error")

I recommend using a Left because it doesn’t make much sense to pass an erroneous value into the Right.

So you really only want the side effects from #doSomethingWithAList()…?

for(is <- getSomeList())
  if(is.nonEmpty) doSomethingWithAList(is)

Including @BalmungSan’s NEL suggestion:

getSomeList().foreach(_.toNel.foreach(doSomethingWithANel))
1 Like