Created
October 17, 2011 02:43
-
-
Save corruptmemory/1291831 to your computer and use it in GitHub Desktop.
Silly example of Kleisli composition of DB operations
This file contains 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
/** | |
* A silly example using Kleisli composition of DB operations | |
* Based on an idea from Runar Bjarnason found here: | |
* https://groups.google.com/d/msg/scala-debate/xYlUlQAnkmE/FteqYKgo2zUJ | |
* | |
* Uses Scalaz7 | |
* | |
* @author <a href="mailto:[email protected]">Jim Powers</a> | |
*/ | |
object Monadic { | |
import scalaz._ | |
import Scalaz._ | |
import effect._ | |
// The initial value `read` is a poor man's simulation of what would get returned form a SELECT statement. | |
case class Connection(read:Int) { | |
def close[S]:StateT[S,IO,Unit] = | |
stateT[S, IO, Unit](s => putStrLn("Closed connection") >>= (_ => ((), s).point[IO])) | |
def open[S]:StateT[S,IO,Connection] = | |
stateT[S, IO, Connection](s => putStrLn("Opened connection") >>= (_ => (this, s).point[IO])) | |
def beginTransaction[S]:StateT[S,IO,Transaction] = | |
stateT[S, IO, Transaction](s => putStrLn("Begin transaction") >>= (_ => (new Transaction(this), s).point[IO])) | |
} | |
// The state value | |
case class Results(r:Int) | |
// In real code the relationship between a Transaction and a Connection would be made more opaque | |
class Transaction(val conn:Connection) { | |
def write[S](x:Int):StateT[S,IO,Transaction] = | |
stateT[S, IO, Transaction](s => putStrLn("Wrote: %d".format(x)) >>= (_ => (this, s).point[IO])) | |
def commit[S]:StateT[S,IO,Connection] = | |
stateT[S, IO, Connection](s => putStrLn("Committed transaction") >>= (_ => (conn, s).point[IO])) | |
} | |
type DBState[A] = StateT[Results,IO,A] | |
type DBRead[A] = Kleisli[Connection,DBState,A] | |
type DBWrite[A] = Kleisli[Transaction,DBState,A] | |
// Brackets connection use. The "types at the ends" of the composition forces transactions to be cleanly completed | |
def withConnection[S](body:Kleisli[Connection,({type λ[x]=StateT[S, IO, x]})#λ,Connection]):Kleisli[Int,({type λ[x]=StateT[S, IO, x]})#λ,Unit] = | |
openConnection[S] >=> body >=> closeConnection[S] | |
def openConnection[S]:Kleisli[Int,({type λ[x]=StateT[S, IO, x]})#λ,Connection] = | |
kleisli[Int,({type λ[x]=StateT[S, IO, x]})#λ,Connection](i => Connection(i).open[S]) | |
def closeConnection[S]:Kleisli[Connection,({type λ[x]=StateT[S, IO, x]})#λ,Unit] = | |
kleisli[Connection,({type λ[x]=StateT[S, IO, x]})#λ,Unit](c => c.close[S]) | |
def addFromConnection:DBRead[Connection] = | |
kleisli[Connection,DBState,Connection](c => for (_ <- modifyT[Results,IO](s => Results(s.r + c.read))) yield c) | |
def addOne:DBRead[Connection] = | |
kleisli[Connection,DBState,Connection](c => for (_ <- modifyT[Results,IO](s => Results(s.r + 1))) yield c) | |
def beginTransaction:DBRead[Transaction] = | |
kleisli[Connection,DBState,Transaction](c => for (t <- c.beginTransaction[Results]) yield t) | |
def endTransaction:DBWrite[Connection] = | |
kleisli[Transaction,DBState,Connection](t => for (c <- t.commit[Results]) yield c) | |
def writeState:DBWrite[Transaction] = | |
kleisli[Transaction,DBState,Transaction](t => getT[Results,IO] flatMap (i => t.write[Results](i.r))) | |
// Simply read a value from the connection and "add two" the hard way. | |
val doRead = | |
addFromConnection >=> addOne >=> addOne | |
// Write whatever state we've accumulated | |
val doWrite = | |
beginTransaction >=> writeState >=> endTransaction | |
// This is an invalid write block because it does not terminate the transaction | |
val halfWrite = | |
beginTransaction >=> writeState | |
def testRead(connectionValue:Int)(initialState:Results):Results = | |
withConnection(doRead)(connectionValue).execT(initialState).unsafePerformIO | |
def testReadWrite(connectionValue:Int)(initialState:Results):Results = | |
withConnection(doRead >=> doWrite)(connectionValue).execT(initialState).unsafePerformIO | |
// Will not typecheck | |
// def testReadHalfWrite(connectionValue:Int)(initialState:Results):Results = | |
// withConnection(doRead >=> halfWrite)(connectionValue).execT(initialState).unsafePerformIO | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment