Skip to content

Commit

Permalink
Monads are back
Browse files Browse the repository at this point in the history
  • Loading branch information
runarorama committed Oct 24, 2012
1 parent ca54e94 commit 0fbd037
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 46 deletions.
30 changes: 14 additions & 16 deletions src/main/scala/com/clarifi/machines/Driver.scala
Expand Up @@ -11,28 +11,26 @@ import scalaz.syntax.monad._
* A `Driver[K]` can step through a machine that requests inputs described by `K`
* and have side-effects at each step.
*/
trait Driver[K] { self =>
trait Driver[M[+_], K] { self =>

/**
* Responds to a single request of type `K`.
*/
def apply(k: K): Option[Any]
def apply(k: K): M[Option[Any]]

/**
* Drives a machine, responding to each request with `apply`, accumulating the
* machine's output according to a `Monoid`, and running side-effects willy nilly.
*/
def drive[A, B](m: Machine[K, A])(g: A => B)
(implicit B: Monoid[B]): B = {
def go(m: Machine[K, A], z: B): B = m match {
case Stop => z
case Emit(a, next) =>
val x = g(a)
go(next(), z |+| x)
case Await(k, s, f) => apply(s) match {
case Some(x) => go(k(x), z)
case None => go(f(), z)
}
def drive[A, B](m: Machine[K, A])(g: A => M[B])
(implicit B: Monoid[B], M: Monad[M]): M[B] = {
def go(m: Machine[K, A], z: B): M[B] = m match {
case Stop => z.pure[M]
case Emit(a, k) =>
val next = k()
g(a) flatMap (x => go(next, z |+| x))
case Await(k, s, f) =>
apply(s).map(_.map(k).getOrElse(f())).flatMap(go(_, z))
}
go(m, B.zero)
}
Expand All @@ -42,9 +40,9 @@ trait Driver[K] { self =>
* the coproduct of `K` and `L`. The resulting driver can drive a machine
* that sends both `K` and `L` requests.
*/
def *[L](d: Driver[L]): Driver[K \/ L] =
new Driver[K \/ L] {
def apply(k: K \/ L): Option[Any] =
def *[L](d: Driver[M, L]): Driver[M, K \/ L] =
new Driver[M, K \/ L] {
def apply(k: K \/ L): M[Option[Any]] =
k.fold(self(_), d(_))
}
}
Expand Down
29 changes: 17 additions & 12 deletions src/main/scala/com/clarifi/machines/Example.scala
@@ -1,6 +1,7 @@
package com.clarifi.machines

import scalaz.{Reader => _, _}
import scalaz.effect._
import Scalaz._

import java.io._
Expand All @@ -10,25 +11,29 @@ object Example {
import Machine.ProcessCategory._
import Plan._

def getFileLines[A](f: File, m: Process[String, A]): Procedure[A] =
new Procedure[A] {
def getFileLines[A](f: File, m: Process[String, A]): Procedure[IO, A] =
new Procedure[IO, A] {
type K = String => Any

val machine = m

def withDriver[R](k: Driver[K] => R): R = {
val r = bufferFile(f)
val d = new Driver[String => Any] {
def apply(k: String => Any): Option[Any] = Option(r.readLine) map k
}
val result = k(d)
r.close
result
def withDriver[R](k: Driver[IO, K] => IO[R]): IO[R] = {
bufferFile(f).bracket(closeReader)(r => {
val d = new Driver[IO, String => Any] {
def apply(k: String => Any) = rReadLn(r) map (_ map k)
}
k(d)
})
}
}

def bufferFile(f: File): BufferedReader =
new BufferedReader(new FileReader(f))
def bufferFile(f: File): IO[BufferedReader] =
IO { new BufferedReader(new FileReader(f)) }

/** Read a line from a buffered reader */
def rReadLn(r: BufferedReader): IO[Option[String]] = IO { Option(r.readLine) }

def closeReader(r: Reader): IO[Unit] = IO { r.close }

def lineCount(fileName: String) =
getFileLines(new File(fileName), Process(_ => 1)).execute
Expand Down
13 changes: 13 additions & 0 deletions src/main/scala/com/clarifi/machines/Machine.scala
Expand Up @@ -6,6 +6,7 @@ package com.clarifi.machines
import scalaz._
import scalaz.syntax.arrow._
import Scalaz._
import \/._

object Machine {

Expand Down Expand Up @@ -44,4 +45,16 @@ object Machine {
*/
def flattened[F[_]:Foldable, K, I](h: Handle[K, F[I]]): Machine[K, I] =
awaits(h) flatMap (is => traversePlan_(is)(emit)) repeatedly

/**
* Evaluate a single step of the machine.
*/
def step[M[+_]:Monad, K, O, A](m: Machine[K, O],
feed: K => M[Option[Any]],
z: O => M[A]): (M[A], Machine[K, O]) \/ M[Machine[K, O]] = m match {
case Stop => right(Stop.pure[M])
case Emit(o, next) => left(z(o) -> next())
case Await(k, s, f) => right(feed(s).map(_.map(k).getOrElse(f())))
}

}
28 changes: 14 additions & 14 deletions src/main/scala/com/clarifi/machines/Procedure.scala
Expand Up @@ -12,41 +12,41 @@ import scalaz.syntax.monad._
* that machine through the use of side-effects. A `Procedure[T]`
* is conceptually a stream of elements of type `T`.
*/
trait Procedure[+A] { self =>
trait Procedure[M[+_], +A] { self =>
type K

def map[B](f: A => B): Procedure[B] =
new Procedure[B] {
def map[B](f: A => B): Procedure[M, B] =
new Procedure[M, B] {
type K = self.K
def machine = self.machine.outmap(f)
def withDriver[R](f: Driver[K] => R) = self withDriver f
def withDriver[R](f: Driver[M, K] => M[R]) = self withDriver f
}

def machine: Machine[K, A]

def withDriver[R](f: Driver[K] => R): R
def withDriver[R](f: Driver[M, K] => M[R]): M[R]

def foldMap[R](f: A => R)(implicit R: Monoid[R]): R =
def foldMapM[R](f: A => M[R])(implicit R: Monoid[R], M: Monad[M]): M[R] =
withDriver(d => d.drive(machine)(f))

def execute[B >: A](implicit B: Monoid[B]): B =
foldMap[B](x => x)
def execute[B >: A](implicit B: Monoid[B], M: Monad[M]): M[B] =
foldMapM[B](M.pure(_))

def andThen[B](p: Process[A, B]): Procedure[B] =
new Procedure[B] {
def andThen[B](p: Process[A, B]): Procedure[M, B] =
new Procedure[M, B] {
type K = self.K
def machine = self.machine andThen p
def withDriver[R](f: Driver[K] => R) = self withDriver f
def withDriver[R](f: Driver[M, K] => M[R]) = self withDriver f
}

def tee[B,C](p: Procedure[B], t: Tee[A, B, C]): Procedure[C] =
new Procedure[C] {
def tee[B,C](p: Procedure[M, B], t: Tee[A, B, C]): Procedure[M, C] =
new Procedure[M, C] {

type K = self.K \/ p.K

def machine = Tee.tee(self.machine, p.machine, t)

def withDriver[R](k: Driver[K] => R): R =
def withDriver[R](k: Driver[M, K] => M[R]): M[R] =
self.withDriver(d1 => p.withDriver(d2 => k(d1 * d2)))
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/com/clarifi/machines/Process.scala
Expand Up @@ -85,4 +85,6 @@ object Process {
def groupingBy[A, K:Order](f: A => K): Process[A, (K, Vector[A])] =
grouping[A]((x, y) => f(x) === f(y)) outmap (v => f(v.head) -> v)

/** Wraps inputs in a Vector */
def wrapping[A]: Process[A, Vector[A]] = grouping((_, _) => true)
}
8 changes: 4 additions & 4 deletions src/main/scala/com/clarifi/machines/package.scala
Expand Up @@ -50,12 +50,12 @@ package object machines {
type Source[+O] = Machine[Nothing, O]

sealed class SourceW[+O](s: Source[O]) {
def procedure: Procedure[O] =
new Procedure[O] {
def procedure[M[+_]]: Procedure[M, O] =
new Procedure[M, O] {
type K = Nothing
def machine = s
def withDriver[R](f: Driver[Nothing] => R) =
f(new Driver[Nothing] {
def withDriver[R](f: Driver[M, Nothing] => M[R]) =
f(new Driver[M, Nothing] {
def apply(k: Nothing) = k
})
}
Expand Down

0 comments on commit 0fbd037

Please sign in to comment.