Created
May 8, 2014 08:20
-
-
Save kafecho/38fcfb8a87f934473aa8 to your computer and use it in GitHub Desktop.
Example of using the Reader Monad.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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