Skip to content

Instantly share code, notes, and snippets.

@corruptmemory
Created October 17, 2011 02:43
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save corruptmemory/1291831 to your computer and use it in GitHub Desktop.
Silly example of Kleisli composition of DB operations
/**
* 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:jim@corruptmemory.com">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