Functional APIs design pattern in action
/* 1. APIs */ | |
trait IO[P[_]] { | |
def read: P[String] | |
def write(msg: String): P[Unit] | |
} | |
object IO { | |
object syntax { | |
def read[P[_]](implicit IO: IO[P]) = IO.read | |
def write[P[_]](msg: String)(implicit IO: IO[P]) = IO.write(msg) | |
} | |
} | |
trait Monad[P[_]] { | |
def flatMap[A, B](pa: P[A])(f: A => P[B]): P[B] | |
def pure[A](a: A): P[A] | |
} | |
object Monad { | |
object syntax { | |
implicit class MonadOps[P[_], A](pa: P[A])(implicit M: Monad[P]) { | |
def flatMap[B](f: A => P[B]) = M.flatMap(pa)(f) | |
def map[B](f: A => B) = M.flatMap(pa)(f andThen M.pure) | |
// aliases | |
def >>=[B](f: A => P[B]) = M.flatMap(pa)(f) | |
def >>[B](next: P[B]) = M.flatMap(pa)(_ => next) | |
} | |
} | |
} | |
/* 1.1 Possible interpreters */ | |
// Console | |
type Id[A] = A | |
type SyncIO = IO[Id] | |
// Async | |
import scala.concurrent.Future | |
type AsyncIO = IO[Future] | |
// Pure state transformations | |
case class IOState(reads: List[String], writes: List[String]) | |
type State[A] = IOState => (IOState, A) | |
type StateIO = IO[State] | |
/* 2. Programs */ | |
/* 2.1 IO programs */ | |
def hello[P[_]](IO: IO[P]): P[Unit] = | |
IO.write("Hello, world!") | |
def sayWhat[P[_]](IO: IO[P]): P[String] = | |
IO.read | |
/* 2.2 IO Monadic programs + syntax */ | |
import IO.syntax._ | |
import Monad.syntax._ | |
def helloSayWhat[P[_]: Monad: IO]: P[String] = | |
write("Hello, say something:") >> | |
read | |
def echo[P[_]: Monad: IO]: P[Unit] = | |
read >>= write[P] | |
def echo2[P[_]: Monad: IO]: P[String] = for { | |
msg <- read | |
_ <- write(msg) | |
} yield msg | |
/* 3. Instances */ | |
implicit object ioTerminal extends IO[Id] { | |
def write(msg: String) = println(msg) | |
def read() = scala.io.StdIn.readLine | |
} | |
implicit object monadId extends Monad[Id] { | |
def flatMap[A, B](pa: Id[A])(f: A => Id[B]): Id[B] = f(pa) | |
def pure[A](a: A): Id[A] = a | |
} | |
/* 4. Composition */ | |
def helloConsole(): Unit = hello[Id](ioTerminal) | |
def sayWhatConsole(): String = sayWhat(ioTerminal) | |
def helloSayWhatConsole() = helloSayWhat(monadId, ioTerminal) | |
def echoConsole() = echo[Id] | |
def echo2Console() = echo2[Id] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment