import scalaz._ import Scalaz._ import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.Await import scala.concurrent.duration._ import ExecutionContext.Implicits.global object FSE extends App { // scalaz doesn't yet have a monad instance for future, here is one. // there is also one in the scalaz-contrib project implicit def futureMonad(implicit executor: ExecutionContext): Monad[Future] with Zip[Future] = new Monad[Future] { override def bind[A, B](fa: Future[A])(f: A ⇒ Future[B]) = fa flatMap f override def point[A](a: ⇒ A) = Future(a) override def map[A, B](fa: Future[A])(f: A ⇒ B) = fa map f } // EitherT is a "Monad Transformer" which combines the effects \/ with some other monad // (In this case, Future) // EitherT wraps a type M[X \/ Y] so that the flatMap method combines both monads, // instead of having the flatMap from Future which takes a A => Future[B], we get a // flatMap which takes a A => Future[String \/ B] type FutureStringError[+A] = EitherT[Future,String,A] /** Return "error".left if the string parses into an int or Return int.right if there is a successful parse */ def parseInt(x: String): FutureStringError[Int] = { EitherT { Future { try { Integer.parseInt(x).right } catch { case e: Throwable => e.getMessage.left } } } } /** Divide numerator by divisor, and return the result as int.right, return "error".left on divide by zero */ def divide(numerator: Int)(divisor: Int): FutureStringError[Int] = { EitherT { Future { if(divisor == 0) "cannot divide by zero".left else (numerator / divisor).right } } } /** combine the operations monadically, combining the effects of both Future and \/ */ def calculate(str: String): FutureStringError[Int] = for { divisor <- parseInt(str) divided <- divide(100)(divisor) } yield(divided) /** extra bonus, since the functions we combine above, both happen to be of the shape, A => M[B], we can combine them using kleisli composition */ val calculate2: String => FutureStringError[Int] = (Kleisli(parseInt _) >=> Kleisli(divide(100))).run def printSuccess(f: Future[Any]) = { f onSuccess { case a => println(a) } Await.result(f, 1 second) } printSuccess(calculate("10").run) // \/-(10) printSuccess(calculate("asdf").run) // -\/(For input string: "asdf") printSuccess(calculate("0").run) // -\/(cannot divide by zero) // These are the same as above printSuccess(calculate2("10").run) printSuccess(calculate2("asdf").run) printSuccess(calculate2("0").run) }