Skip to content

Instantly share code, notes, and snippets.

@bhuemer
Last active August 29, 2015 14:04
Show Gist options
  • Save bhuemer/b31e4ca21d2d0ea9e363 to your computer and use it in GitHub Desktop.
Save bhuemer/b31e4ca21d2d0ea9e363 to your computer and use it in GitHub Desktop.
package at.bhuemer.fpis.chapter11
import scala.concurrent.{Await, ExecutionContext, Future}
/**
*
*/
trait Monad[F[_]] { self =>
// The primitives that any monad implementation must provide
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)))
// Ex. 3:
def sequence[A](mas: List[F[A]]): F[List[A]] =
mas match {
case Nil => unit(Nil)
case head :: tail => for {
a <- head
as <- sequence(tail)
} yield a :: as
}
def traverse[A, B](la: List[A])(f: A => F[B]): F[List[B]] =
sequence(la map f)
// Ex. 4/5:
def replicateM[A](n: Int, ma: F[A]): F[List[A]] =
sequence(List.fill(n)(ma))
// Ex. 6
def filterM[A](as: List[A])(f: A => F[Boolean]): F[List[A]] =
as match {
case Nil => unit(Nil)
case head :: tail => for {
keep <- f(head)
filtered <- filterM(tail)(f)
} yield {
if (keep) head :: filtered
else filtered
}
}
// The more complicated version I had in the beginning
def filterF[A](as: List[A])(f: A => F[Boolean]): F[List[A]] = {
// This first step triggers the calculation for each element in the list and zips it together in a tuple with the element itself ..
val evaluationTriggered: List[F[(A, Boolean)]] = as map { a => f(a) map { (a, _) } }
// .. now we need to wait for results by sequencing the whole thing together ..
val evaluationSequenced: F[List[(A, Boolean)]] = sequence(evaluationTriggered)
// .. and finally we evaluate those results.
val evaluationFiltered: F[List[A]] = evaluationSequenced map { list => list.filter(_._2).map(_._1) }
evaluationFiltered
}
// Ex. 7
def compose[A, B, C](fa: A => F[B], fb: B => F[C]): A => F[C] = fa(_) flatMap fb
def flatMapC[A, B](ma: F[A])(f: A => F[B]): F[B] = compose[F[A], A, B](identity, f)(ma)
// Ex. 9?
def flatten[A](mma: F[F[A]]): F[A] = join(mma)
def join[A](mma: F[F[A]]): F[A] =
mma flatMap identity
def composeJ[A, B, C](fa: A => F[B], fb: B => F[C]): A => F[C] = a => flatten(fa(a) map fb)
def flatMapJ[A, B](ma: F[A])(f: A => F[B]): F[B] = flatten(ma map f)
// Utilities:
implicit class MonadOps[A](ma: F[A]) {
def flatMap[B](f: A => F[B]): F[B] = self.flatMap(ma)(f)
def map[B](f: A => B): F[B] = self.map(ma)(f)
}
}
// Ex. 1 (parts of it)
object OptionMonad extends Monad[Option] {
override def unit[A](a: A): Option[A] = Some(a)
override def flatMap[A, B](ma: Option[A])(f: A => Option[B]): Option[B] = ma flatMap f
override def map[A, B](ma: Option[A])(f: A => B): Option[B] = ma map f
}
object ListMonad extends Monad[List] {
override def unit[A](a: A): List[A] = List(a)
override def flatMap[A, B](ma: List[A])(f: A => List[B]): List[B] = ma flatMap f
override def map[A, B](ma: List[A])(f: A => B): List[B] = ma map f
}
class FutureMonad(implicit ex: ExecutionContext) extends Monad[Future] {
override def unit[A](a: A): Future[A] = Future { a }
override def flatMap[A, B](ma: Future[A])(f: A => Future[B]): Future[B] = ma flatMap f
override def map[A, B](ma: Future[A])(f: A => B): Future[B] = ma map f
}
object FutureMonadApp {
import ExecutionContext.Implicits.global
import scala.concurrent.duration._
def slowGreaterThanFilter(threshold: Int)(value: Int): Future[Boolean] = Future {
println(s"Checking $value")
Thread.sleep(100)
value > threshold
}
def main(args: Array[String]): Unit = {
val futureMonad = new FutureMonad()
val values = (1 to 100).toList
val filteredM = futureMonad.filterM(values)(slowGreaterThanFilter(50))
println("filteredM: " + Await.result(filteredM, 10 minutes))
val filteredF = futureMonad.filterF(values)(slowGreaterThanFilter(50))
println("filteredF: " + Await.result(filteredF, 10 minutes))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment