For-Comprehension Example with \/ and IO

457 views Asked by At

Given the following Foo case class:

scala> case class Foo(x: Int)
defined class Foo

I check if it's a valid Foo before constructing it inside of validateFoo:

scala> def validateFoo(foo: Foo): \/[String, Foo] = 
              (if(foo.x > 0) foo.success[String] 
              else ("invalid foo").failure[Foo]).disjunction
validateFoo: (foo: Foo)scalaz.\/[String,Foo]

Lastly, f creates a Foo, and then tries to perform an IO action (example: save Foo to database).

scala> def f(x: Foo): IO[\/[String,Int]] = for {
     |    foo <- validateFoo(x)
     |    ioFoo <- IO { foo }
     | } yield ioFoo
<console>:19: error: type mismatch;
 found   : scalaz.effect.IO[Foo]
 required: scalaz.\/[?,?]
          ioFoo <- IO { foo }
                ^
<console>:18: error: type mismatch;
 found   : scalaz.\/[String,Nothing]
 required: scalaz.effect.IO[scalaz.\/[String,Int]]
          foo <- validateFoo(x)
              ^

However, I ran into the above issue when trying to chain the bind's together in the for-comprehension.

The problem, as I see it, is that the proper return type is IO[\/[String, Int]]. However, I don't know how to validate Foo and handle it in the IO monad with a for-comprehension.

2

There are 2 answers

0
Travis Brown On BEST ANSWER

You can't combine String \/ A and IO[A] computations like this—if you desugared your for-comprehension you'd see that the types just don't work out.

If you really wanted to, you could use a monad transformer to compose the two monads. Suppose we have the following setup, for example (which is essentially your code, but cleaned up a bit, and with a new method representing the database operation):

import scalaz._, Scalaz._, effect._

case class Foo(x: Int)

def validateFoo(foo: Foo): String \/ Foo =
  (foo.x > 0) either foo or "invalid foo"

def processFoo(foo: Foo): IO[Foo] = IO {
  println(foo)
  foo
}

Now we can write the following:

type IOStringOr[A] = EitherT[IO, String, A]

def f(x: Foo): IOStringOr[Foo] = for {
  foo <- EitherT(validateFoo(x).point[IO])
  ioFoo <- processFoo(foo).liftIO[IOStringOr]
} yield ioFoo

Now you can run the EitherT to get an IO action:

val ioAction: IO[String \/ Foo] = f(Foo(10)).run

That's how you could use \/ and IO together in a for-comprehension. It's pretty definitely overkill in your case, though. I'd probably write something like this:

def f(x: Foo): IO[String \/ Foo] =
  validateFoo(x).bitraverse(_.point[IO], processFoo)

This uses the Bitraverse instance for \/ to map a function into IO over both sides of the disjunction and then turn the whole thing inside out.

2
rgcase On

It looks like you have an error in your definition of validateFoo. I think you want

scala> def validateFoo(foo: Foo): \/[String, Foo] = 
          (if(foo.x > 0) foo.success[Foo] 
          else ("invalid foo").failure[String]).disjunction

You've mixed up the String and Foo in the success and failure cases.