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.