You can write a simple function, but it's gonna log only first absence of value inside Option
(due to sequential nature of for-comprehension
):
def logEmpty[T](opt: Option[T], msgIfNone: String) = {
if (opt.isEmpty) println(msgIfNone) //or something like logger.warn
opt
}
Usage:
for {
a <- logEmpty(aOption, "Sorry no a")
b <- logEmpty(bOption, "Sorry no b")
c <- logEmpty(cOption, "Sorry no c")
d <- logEmpty(dOption, "Sorry no d")
} yield {...process...}
DSL-like:
implicit class LogEmpty[T](opt: Option[T]) {
def reportEmpty(msg: String) = {
if (opt.isEmpty) println(msg)
opt
}
}
Usage:
for {
a <- aOption reportEmpty "Sorry no a"
b <- bOption reportEmpty "Sorry no b"
c <- cOption reportEmpty "Sorry no c"
d <- dOption reportEmpty "Sorry no d"
} yield {a + b + c + d}
Example:
scala> for {
| a <- Some("a") reportEmpty "Sorry no a"
| b <- None reportEmpty "Sorry no b"
| c <- Some("c") reportEmpty "Sorry no c"
| d <- None reportEmpty "Sorry no d"
| } yield {a + b + c + d}
Sorry no b
res19: Option[String] = None
If you need to report more - the best way is to use Validation
from scalaz or Validated
from cats, so your message about the abscence is gonna be represented as invalid state of Validated
. You can always convert Validated
to Option
.
Solution:
import cats._
import cats.data.Validated
import cats.data.Validated._
import cats.implicits._
implicit class RichOption[T](opt: Option[T]) {
def validOr(msg: String) =
opt.map(Valid(_)).getOrElse(Invalid(msg)).toValidatedNel
}
Example:
val aOption = Some("a")
val bOption: Option[String] = None
val cOption: Option[String] = None
scala> aOption.validOr("no a") |+| bOption.validOr("no b") |+| cOption.validOr("no c")
res12: cats.data.Validated[cats.data.NonEmptyList[String],String] = Invalid(NonEmptyList(no b, no c))
scala> aOption.validateOr("no a") |+| aOption.validateOr("no a again")
res13: cats.data.Validated[cats.data.NonEmptyList[String],String] = Valid(aa)
I used |+|
operator assuming concatenation, but you can use applicative builders (or just zip
) as well in order to implement other operation over option's content:
scala> (aOption.validOr("no a") |@| aOption.validOr("no a again")) map {_ + "!" + _}
res18: cats.data.Validated[cats.data.NonEmptyList[String],String] = Valid(a!a)
scala> (aOption.validOr("no a") |@| bOption.validOr("no b") |@| cOption.validOr("no c")) map {_ + _ + _}
res27: cats.data.Validated[cats.data.NonEmptyList[String],String] = Invalid(NonEmptyList(no b, no c))
Both cat's Xor
and Validated
are variations of scala's Either
, but the difference between Xor
and Validated
is that Xor
(and Either
) is more adopted for "fail-fast" monadic approach (for comprehensions aka do-notation) in contrast to Validated
that is using applicative approach (which allows |@|
and zip
). flatMap
is considered as sequential operator, |@|
/zip
are considered as parallel operator (don't confuse with execution model - it's orthogonal to the nature of operator). You can read more in cats documentation: Validated, Xor.