[SOLVED] For comprehension yielding Unit

I have a situation where I want to perform a series of operations (with side-effects) where I am only interested if they produced an error or not, and therefore their return type would be an Option[Error].

I want to chain several of those operations in a for comprehension, which leads to something like that:

def foo(data: Data): Option[Error] = {
  val result = for {
    _ <- op1(data).toLeft(())
    _ <- op2(data).toLeft(())
  } yield ()
  
  result.swap.toOption
}

def op1(data: Data): Option[Error] = ???
def op2(data: Data): Option[Error] = ???

It works but it seems not right. Is there a better way to implement something like that?

1 Like

You want to stop at the first error or would you like to collect all of them?
Assuming you only want the first error, how important would it be to avoid executing the others?
What libraries are you open to use? Like cats, cats-effect & fs2?
What parts of the code can be modified?
And ultimately what does “better” means for you in this case?

I don’t follow the “therefore”. A more appropriate type than Option[Error] seems to be Either[Error, Unit] (which is what you’re creating as an intermediate result in your transformation). What’s wrong with that?

2 Likes

I do want to stop at the first error and all subsequent operations MUST not be executed after.

I use vanilla Scala.

All of foo can be modified.

I had the feeling that using Unit as the type of Right would be odd.

But you are using it like this inside your transformation already. :slight_smile:

Unit is the standard way of declaring that a function won’t produce an actionable result value but only exists for its side effects. “Lifting” this to Either's RHS means that there’ll be no actionable value for the result of a successful computation. This isn’t any more odd than plain Unit as a result type.

Cats et al. provide some sugar for ignoring the results in a successful computation chain so you can write something like

op1("x") >> op2("x")

(You can implement this on your own, of course, based on #flatMap().)

Of course you could implement your own ADT whose API directly encodes the desired behavior, without threading Unit through everywhere. But I’d think Either is just fine and won’t puzzle anybody.

1 Like

…but op1 and op2 cannot be modified and encode their failure mode via Option? I’d think that’s unconventional to say the least, and I’d immediately convert to Either at the boundaries of my code base.

2 Likes

If only foo can be modified, I really do not see any other / better way of doing it.

I agree with @sangamon that an Either[Error, Unit] would be better, at the end that is similar to an IO[Unit] or a Future[Unit].

2 Likes

Now, knowing that Either[Error, Unit] is acceptable I redid the implementation of op1 and op2, too and it looks better now.

Thank you for your help!

1 Like