fr3ak
fr3ak

Reputation: 503

Combining List, Future and Option in for-comprehension - scalaz

I've got following problem:

val sth: Future[Seq[T, S]] = for {
  x <- whatever: Future[List[T]]
  y <- x: List[T]
  z <- f(y): Future[Option[S]]
  n <- z: Option[S]
} yield y: T -> n: S

I would want to make this code to work(I guess everyone understands the idea as I've added types).

By "to work" I mean, that I would want to stay with the for-comprehension structure and fulfil expected types in the end. I know there are "ugly" ways to do it, but I want to learn how to do it pure :)

As I read the internet I've reached the conclusion that my problem may be solved by the monad transformers & scalaz. Unfortunately, I couldn't find an example to help understand better how should I proceed.

At the moment I've tried scalaz and Eff monad libs, but I guess I still don't understand how it works because I couldn't solve my problem.

I will be grateful for any help.

EDIT: It supposed to be future of sequence, also regarding the "whatever" I get it as a parameter of the function, sorry for misleading you

Upvotes: 3

Views: 1098

Answers (2)

Aivean
Aivean

Reputation: 10882

The problem with for comprehension is that it's not some kind of magic monadic "unwrapper", it's just a sequence of map, flatMap and filter.

As you may know map and flatMap operate only on "inner" type, leaving "outer" type of monad unchanged. This means you can't do this:

for {
  x <- whatever: Future[List[T]]
  y <- x: List[T]
} yield y

inside single for. Instead, you can do something like this:

for (x <- whatever: Future[List[T]])
  yield for (y <- x: List[T]) yield y

Which looks kinda ugly.

Back to your case, I's easier to write whole transformation explicitly using map and flatMap, as it gives you greater visibility and control:

whatever.flatMap {
  x: List[T] =>
    Future.sequence(x.map {
      y: T => f(y).map(y -> _)
    }).map(_.collect {
      case (y, Some(n)) => y -> n
    })
}

Also, @trustnoone mentioned, you can't get rid of the Future without explicitly calling Await.

Upvotes: 1

Giovanni Caporaletti
Giovanni Caporaletti

Reputation: 5556

You could do something like what you need using the scalaz ListT monad transformer

 object Test {
   import scalaz._
   import ListT._
   type T = String
   type S = Int
   val whatever: Future[List[T]] = ??? // you get this somewhere
   def f(y: T): Future[Option[S]] = ??? // function that returns future of option

   val sth: Future[List[(T, S)]] = (for {
     y <- listT(whatever) 
     // you cannot mix list and option, but you can convert the option to a list of 1 item
     n <- listT(f(y).map(_.toList)) 
   } yield y -> n).run
 }

N.B.: Since you start with a future, you cannot return a Seq[(T,S)], you can only have a future. You have to call Await.result if you want to block and get the result.

Upvotes: 3

Related Questions