sealed trait Free[F[_], A] {
import Free._
def map[B](f: A => B): Free[F, B] =
{ a => pure(f(a)) }
flatMap
def flatMap[B](f: A => Free[F, B]): Free[F, B] =
Bind(this, f)
def foldMap[G[_]: Monad](nt: F ~> G): G[A] =
this match {
case Pure(a) => implicitly[Monad[G]].pure(a)
case Suspend(fa) => nt(fa)
case Bind(fa, f) => val mg = implicitly[Monad[G]]
val ga = fa.foldMap(nt)
.flatMap(ga)(f(_).foldMap(nt))
mg}
}
object Free {
def pure[F[_], A](a: A): Free[F, A] =
Pure(a)
def liftM[F[_], A](fa: F[A]): Free[F, A] =
Suspend(fa)
final case class Pure[F[_], A](a: A) extends Free[F, A]
final case class Suspend[F[_], A](fa: F[A]) extends Free[F, A]
final case class Bind[F[_], A, B]( fa: Free[F, A]
, f: A => Free[F, B]
) extends Free[F, B]
}
sealed trait Console[A]
case class Print(x: String) extends Console[Unit]
case object ReadLn extends Console[String]
val program: Free[Console, Unit] =
for {
<- Free.liftM(Print("First name: "))
_ <- Free.liftM(ReadLn)
first <- Free.liftM(Print("Last name: "))
_ <- Free.liftM(ReadLn)
last <- Free.liftM(Print(s"Hello, $first $last!\n"))
_ } yield ()
.foldMap[ID](new ~>[Console, ID]{
programdef apply[A](c: Console[A]): ID[A] =
match {
c case Print(x) => print(x)
case ReadLn => readLine
}
})
This file is literate Scala, and can be run using Codedown:
$ curl \
https://earldouglas.com/posts/type-classes/applicative.md \
https://earldouglas.com/posts/type-classes/functor.md \
https://earldouglas.com/posts/type-classes/id.md \
https://earldouglas.com/posts/type-classes/monad.md \
https://earldouglas.com/posts/type-classes/natural-transformation.md \
https://earldouglas.com/posts/type-classes/free.md |
codedown scala > script.scala
$ scala -nc script.scala
First name: James
Last name: Douglas
Hello, James Douglas!