Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling fail-fast failures when the return type is Option[Error]

Tags:

scala

scalaz

I have posted quite several questions about failure handling in Scala and I really thank you all for your answers.

I understand my options when dealing with Either and Scalaz or a for comprehension, and I have another (last?) question:

How to do a fail-fast sequence of operations when the operations are dealing with the outside non-functional world, like a DB?


I mean I have a method like that:

def insertItem(item: Item): Either[Error,Item]

Thanks to Either and these answers, I know how to do it with Either: Chaining method calls with Either and Method parameters validation in Scala, with for comprehension and monads

But my Item case class is immutable and it doesn't really make sense to return it as a Right since the caller already has the value.

Thus how can I do the same kind of thing with:

def insertItem(item: Item): Option[Error]

In my application, when an user is created, we also create some items for him. If an item fails to create, then the whole process should stop.

When I use directly Option[Error] in a for comprehension, I don't think I'll get the result I expect.

I guess it makes sense to do something like that:

for {
  _ <- insertItem(item1).toLeft("???").right
  _ <- insertItem(item2).toLeft("???").right
  _ <- insertItem(item3).toLeft("???").right
}

But as the values "???" I put in my Right are never useful, I guess I'm missing the elegant solution which do not involve creating Rights that will never be used.

I think I'm looking for something that will continue the for comprehension only when the result is None, which is kind of weird because I just want to continue to the next operation, and not do a real map operation.

By the way, if possible, I would like both non-Scalaz and Scalaz solutions. I'm not sure Scalaz handle such things, because it seems more focused on real functional programming and perhaps do not provide code for side-effect behaviors like my use case?

like image 650
Sebastien Lorber Avatar asked Dec 16 '22 18:12

Sebastien Lorber


2 Answers

I don't see a principled reason not to use Either[Error, Unit] in a case like this (or at least I've done it, and I don't feel guilty about it). Say we have the following:

def launch(thing: String): Either[String, Unit] = Either.cond(
  thing.nonEmpty,
  println("Launching the " + thing),
  "Need something to launch!"
)

We can show that the right projection monad is appropriately lazy:

scala> for {
     |   _ <- launch("flowers").right
     |   _ <- launch("").right
     |   r <- launch("MISSILES").right
     | } yield r
Launching the flowers
res1: Either[String,Unit] = Left(Need something to launch!)

No missiles get launched, as desired.


It's worth noting that if you use Option instead of Either, the operation you're describing is just the sum given the "First" monoid instance for Option (where the addition operation is just orElse). For example, we can write the following with Scalaz 7:

import scalaz._, Scalaz._

def fst[A](x: Option[A]): Option[A] @@ Tags.First = Tag(x)

def launch(thing: String): Option[String] = if (thing.isEmpty) Some(
  "Need something to launch!"
) else {
  println("Launching the " + thing)
  None
}

And now:

scala> val r: Option[String] = fst(launch("flowers")) |+| fst(
     |   launch("")) |+| fst(launch("MISSILES"))
Launching the flowers
r: Option[String] = Some(Need something to launch!)

Once again, no missiles.

like image 178
Travis Brown Avatar answered Feb 15 '23 10:02

Travis Brown


We do something similar where I work with scalaz to fail fast with message sending, though this is not idiomatic scalaz:

def insertItem(item: Item): Validation[Error, Unit]

val result = for {
  _ <- insertItem(item1)
  _ <- insertItem(item2)
  _ <- insertItem(item3)
} yield Unit
like image 23
Noah Avatar answered Feb 15 '23 10:02

Noah