Niklas Vest
Niklas Vest

Reputation: 902

Switching monad within a for comprehension

I am trying to use a for-comprehension to process instances of Options. 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

Answers (3)

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

jwvh
jwvh

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

Tim
Tim

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

Related Questions