Reputation: 902
I am trying to use a for-comprehension to process instances of Option
s. However, as my last step, a function I use doesn't return an option but a List[Option[Something]]
(Note, not an OptionT[List, Something]
). I only want to proceed if all options in the list are instances of Some
. Is there a clean way to process this as part of the flatMap calls? Currently I am set up like this:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2)
list <- if (rawList.forall(_.isDefined))
Some(rawList.map(_.get))
else
None
} { /* processing list */ }
I am sorry in case this question has been asked but I don't know the terminology of what I am looking for so I can't search the web effectively in this regard.
In case I am missing an essential theoretical point, please provide pointers to literature or other resources on the thing I seem to be having troubles understanding.
Upvotes: 2
Views: 152
Reputation: 22840
Short answer, you can not mix Monads. Remember for
is just sugar syntax for flatMap
which has a signature like (F[A], A => F[B]) => F[B]
So you have to had the same Monad outside and inside.
Also, if you have a list of options and you want an option of list, you can use sequence
(from cats)
def processList(rawList: List[Foo]): List[Bar] = ???
val result: Option[List[Bar]] = for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList <- businessLogic3(o2).sequence
} yield processList(rawList)
If you are not using cats and do not want to include it, you can create your own sequence
extension method pretty easily just for a list of options.
implicit class ListOps[A](private val list: List[Option[A]]) extends AnyVal {
def sequence: Option[List[A]] = {
@annotation.tailrec
def loop(remaining: List[Option[A]], acc: List[A]): Option[List[A]] =
remaining match {
case Some(a) :: xs => loop(remaining = xs, a :: acc)
case None :: _ => None
case Nil => Some(acc.reverse)
}
loop(remaining = list, acc = List.empty)
}
}
Upvotes: 2
Reputation: 51271
If /* process list */
is done only for the side effects, i.e. no yield
, and the ultimate goal is to process the individual elements of list
, unwrapped from their Some(x)
status, then you might do something like this.
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
lst = businessLogic3(o2)
if lst.forall(_.nonEmpty)
x <- lst.flatten
} {/* process x */}
Upvotes: 3
Reputation: 27356
This is a cleaner way of doing the test:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2) if rawList.forall(_.isDefined)
list = rawList.flatten
} {/* processing list */}
Alternatively you can create the flattened list first and then compare sizes:
for {
o1 <- businessLogic1
o2 <- businessLogic2(o1)
rawList = businessLogic3(o2)
list = rawList.flatten if list.size == rawList.size
} {/* processing list */}
Upvotes: 4