Skip to content

Instantly share code, notes, and snippets.

@stew
Last active December 18, 2015 08:19
Show Gist options
  • Save stew/5752733 to your computer and use it in GitHub Desktop.
Save stew/5752733 to your computer and use it in GitHub Desktop.
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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment