Skip to content

Instantly share code, notes, and snippets.

@kafecho
Created May 8, 2014 08:20
Show Gist options
  • Save kafecho/38fcfb8a87f934473aa8 to your computer and use it in GitHub Desktop.
Save kafecho/38fcfb8a87f934473aa8 to your computer and use it in GitHub Desktop.
Example of using the Reader Monad.
package org.kafecho.learning.monad
import java.util.UUID
/** My attempt at implementing the Reader Monad concept.
* The Reader Monad encapsulates a computation which:
* - requires a dependency of type D
* - produces values of type A.
*/
case class Reader[D, A](computation: D => A) {
/* To actually compute the value, once the dependency is provided.*/
def run(d: D) : A = computation(d)
/* Given a function fn: A => B, you use map to create a Reader[D, B].*/
def map[B](fn: A => B): Reader[D, B] = {
def newComputation(d: D): B = {
fn(computation(d))
}
Reader(newComputation)
}
/* Given a function fn: A => Reader[D,B], you use flatMap to create a Reader[D,B]*/
def flatMap[B](fn: A => Reader[D, B]): Reader[D, B] = {
def result(d: D) : B = {
val a = computation(d)
val reader = fn(a)
reader.computation(d)
}
Reader(result)
}
}
case class Bundle(uuid: UUID, author: String, descriptions: List[String])
trait BundleStore {
def loadBundle(uuid: UUID): Bundle
def saveBundle(bundle: Bundle): Bundle
}
class InMemoryBundleStore extends BundleStore{
var map : Map[UUID, Bundle] = Map()
def loadBundle(uuid: UUID) = map(uuid)
def saveBundle(bundle: Bundle) = {
map += (bundle.uuid -> bundle)
bundle
}
}
/** Primitive readers */
trait BundleOperations {
def loadBundle(uuid: UUID) = Reader { bundleStore: BundleStore =>
bundleStore.loadBundle(uuid)
}
def saveBundle(bundle: Bundle) = Reader { bundleStore: BundleStore =>
bundleStore.saveBundle(bundle)
}
}
/** The operations below all require a bundle store to eventually be computed.
* However, that dependency does not appear in the method signatures.
*/
trait MetadataOperations extends BundleOperations {
def updateAuthor(uuid: UUID, author: String) =
for {
bundle <- loadBundle(uuid);
updated = bundle.copy(author = author);
saved <- saveBundle(updated)
} yield saved
def isDescribed(uuid: UUID) = {
loadBundle(uuid).map(bundle => !bundle.descriptions.isEmpty)
}
def updateDescriptions(uuid: UUID, descriptions: List[String]) =
for {
bundle <- loadBundle(uuid);
updated = bundle.copy(descriptions = descriptions);
saved <- saveBundle(updated)
} yield saved
}
object TestReaderMonad extends App with MetadataOperations{
val bundleStore = new InMemoryBundleStore
val uuid = UUID.randomUUID
val bundle = Bundle(uuid, "Richard", Nil)
bundleStore.saveBundle(bundle)
val operation1 = updateAuthor(uuid, "Guillaume")
val operation2 = updateDescriptions(uuid, List("Hello", "World"))
// The dependency is only injected when you run the computation.
println (operation1.run(bundleStore))
println (operation2.run(bundleStore))
println ( isDescribed(uuid).run(bundleStore))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment