=> m.map(my) { y => x + y } } If we define a Monad trait and implicit instances of it, e.g. for Option, Future and List, then it is possible to use the following method to sum a pair of optional integers, future integers, or integer lists. See the next slide for details. But is it possible to do something similar when the body of the sum method is a for comprehension? It is! Daniel Spiewak shows us how in the slide after next. def sum[M[_]](mx:M[Int],my:M[Int])(implicit m: Monad[M]): M[Int] = for { x <- mx y <- my } yield x + y @philip_schwarz TIL - a polymorphic function able to sum integers using a for comprehension because it abstracts over the Monad providing the summands Abstracting over the Monad yielded by a for comprehension and its generators
B](ma: F[A])(f: A => F[B]): F[B] def map[A, B](ma: F[A])(f: A => B): F[B] = flatMap(ma)(a => unit(f(a))) } If we define a Monad trait like this def sum[M[_]](mx:M[Int], my:M[Int])(implicit m: Monad[M]): M[Int] = m.flatMap(mx) { x => m.map(my) { y => x + y } } Then we are able to define sum like this implicit val optionMonad = new Monad[Option] { def unit[A](a: => A): Option[A] = Some(a) def flatMap[A, B](ma: Option[A])(f: A => Option[B]): Option[B] = ma flatMap f } implicit val listMonad = new Monad[List] { def unit[A](a: => A): List[A] = List(a) def flatMap[A, B](ma: List[A])(f: A => List[B]): List[B] = ma flatMap f } import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global implicit val futureMonad = new Monad[Future] { def unit[A](a: => A): Future[A] = Future(a) def flatMap[A, B](ma: Future[A])(f: A => Future[B]): Future[B] = ma flatMap f } and once we have created some implicit Monad instances, e.g. for Option, List and Future… …we can use them to sum the integer content of any pair of monadic values def sum[M[_]](mx:M[Int],my:M[Int])(implicit m: Monad[M]): M[Int] = for { x <- mx y <- my } yield x + y Note however that we cannot do the same with the for comprehension. Our monad instances are of no use in the for comprehension and the compiler does not consider monadic parameters mx and my to have a map and a flatMap that it can use to desugar the for comprhension. o o assert( sum( Option(3), Option(5) ) == Option(8) ) assert( sum( List(1,2), List(3,4) ) == List(4,5,5,6) ) import scala.concurrent.Await import scala.concurrent.duration.Duration val result = Await.result(sum( Future(3), Future(5) ), Duration.Inf) assert( result == 8 )
mx y <- my } yield x + y It turns out however that it is possible to get this very same sum method to work! Daniel Spiewak @djspiewak It can be done using the following approach described by Daniel Spiewak in his 2010 answer to Stack Overflow question Monad Trait in Scala https://stackoverflow.com/a/1993982/1325960. I did make some small changes to the code to get it to compile. trait Monad[M[_]] { def unit[A](a: A): M[A] def bind[A, B](m: M[A])(f: A => M[B]): M[B] } implicit def monadicSyntax[M[_], A, B](m: M[A])(implicit tc: Monad[M]) = new { def map[B](f: A => B): M[B] = tc.bind(m)((a) => tc.unit(f(a)) ) def flatMap[B](f: A => M[B]): M[B] = tc.bind(m)(f) } implicit object MonadicOption extends Monad[Option] { override def unit[A](a: A) = Some(a) override def bind[A, B](ma: Option[A])(f: A => Option[B]): Option[B] = ma flatMap f } We can now exercise the sum method, e.g. use it to sum two optional integers. You would of course define similar implicit objects for any other monad your heart desires. In Haskell terms, you can think of Monad like the typeclass and MonadicOption as a particular instance of that type class. The monadicSyntax implicit conversion simply demonstrates how this typeclass could be used to allow the use of Scala's for-comprehensions with anything which satisfies the Monad typeclass. Generally speaking, most things in the Scala standard library which implement flatMap are monads. Scala doesn't define a generic Monad typeclass (though that would be very useful). Instead, it relies on a syntactic trick of the parser to allow the use of for- comprehensions with anything which implements the appropriate methods. Specifically, those methods are map, flatMap and filter (or foreach and filter for the imperative form). 3rd Jan 2010
= List(a) override def bind[A, B](ma: List[A])(f: A => List[B]): List[B] = ma flatMap f } assert( sum(List(1,2),List(3,4)) == List(4,5,5,6) ) import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global implicit object MonadicFuture extends Monad[Future] { override def unit[A](a: A) = Future(a) override def bind[A, B](ma: Future[A])(f: A => Future[B]): Future[B] = ma flatMap f } import scala.concurrent.duration.Duration import scala.concurrent.Await val result = Await.result(sum( Future(3), Future(5) ), Duration.Inf) assert( result == 8 ) Now let’s sum two Future integers and two lists of integers