Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Abstracting over the Monad yielded by a for comprehension and its generators
1. 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
}
}
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
2. trait Monad[F[_]] {
def unit[A](a: => A): F[A]
def flatMap[A, 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 )
3. def sum[M[_]](mx:M[Int],my:M[Int])(implicit m: Monad[M]): M[Int] =
for {
x <- 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
4. implicit object MonadicList extends Monad[List] {
override def unit[A](a: A) = 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