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

风格不统一 提交于 2019-12-05 17:39:46

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.

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

If you want to chain together methods that contain Option[Error] and only execute the next step if the Option is None, you can use orElse for that.

If you are only interested in the first failure, than an Iterator can be your choice:

case class Item(i: Int)

case class Error(i: Item)

val items = Iterator(1,3,7,-5,-2,0) map Item

def insertItem(item: Item): Option[Error] =
  if (item.i < 0) Some(Error(item)) else None

scala> val error = (items map insertItem find (_.isDefined)).flatten
error: Option[Error] = Some(Error(Item(-5)))

If insertItem is not your only method it is possible to call them separated with your values:

val items = Iterator(
  () => insertItem(i1),
  () => deleteItem(i2),
  () => insertItem(i3),
  () => updateItem(i4))
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!