Connection Management: FP Style! Jed Wesley-Smith @jedws Jed - - PowerPoint PPT Presentation

connection management fp style jed wesley smith jedws jed
SMART_READER_LITE
LIVE PREVIEW

Connection Management: FP Style! Jed Wesley-Smith @jedws Jed - - PowerPoint PPT Presentation

Connection Management: FP Style! Jed Wesley-Smith @jedws Jed Wesley-Smith @jedws Jed Wesley-Smith @jedws Jed Wesley-Smith @jedws our problem our problem statistical analysis our problem statistical analysis heavy lifting in


slide-1
SLIDE 1

Connection Management: FP Style!

slide-2
SLIDE 2

Jed Wesley-Smith @jedws

slide-3
SLIDE 3

Jed Wesley-Smith @jedws

slide-4
SLIDE 4

Jed Wesley-Smith @jedws

slide-5
SLIDE 5

Jed Wesley-Smith @jedws

slide-6
SLIDE 6
  • ur problem
slide-7
SLIDE 7
  • ur problem
  • statistical analysis
slide-8
SLIDE 8
  • ur problem
  • statistical analysis
  • heavy lifting in R
slide-9
SLIDE 9
  • ur problem
  • statistical analysis
  • heavy lifting in R
  • manage external process
slide-10
SLIDE 10
  • ur problem
  • statistical analysis
  • heavy lifting in R
  • manage external process
  • connect to external process
slide-11
SLIDE 11
  • ur problem
  • statistical analysis
  • heavy lifting in R
  • manage external process
  • connect to external process
  • connection is stateful
slide-12
SLIDE 12
slide-13
SLIDE 13

first pass: imperative

slide-14
SLIDE 14

first pass: imperative

trait Connection {

slide-15
SLIDE 15

first pass: imperative

trait Connection { def eval(expr: String): REXP

slide-16
SLIDE 16

first pass: imperative

trait Connection { def eval(expr: String): REXP def close() }

slide-17
SLIDE 17

first pass: imperative

trait Connection { def eval(expr: String): REXP def close() } trait Server { // AKA ConnectionFactory def connection: Connection }

slide-18
SLIDE 18

first pass: imperative

slide-19
SLIDE 19

first pass: imperative

  • manual
  • Server management
  • Connection management
  • Connection closing (in finally)
slide-20
SLIDE 20

first pass: imperative

  • manual
  • Server management
  • Connection management
  • Connection closing (in finally)
  • hard to implement pooling
slide-21
SLIDE 21

second pass: loaner

slide-22
SLIDE 22

second pass: loaner

trait Connection { def eval(expr: String): REXP }

slide-23
SLIDE 23

second pass: loaner

trait Connection { def eval(expr: String): REXP } trait Server { def withConnection[A](f: Connection => A): A }

slide-24
SLIDE 24

second pass: loaner

slide-25
SLIDE 25

second pass: loaner

  • better
  • don’t manage Connection
  • don’t need to close Connection
slide-26
SLIDE 26

second pass: loaner

  • better
  • don’t manage Connection
  • don’t need to close Connection
  • manual
  • Server management
  • composition
slide-27
SLIDE 27

second pass: loaner

  • better
  • don’t manage Connection
  • don’t need to close Connection
  • manual
  • Server management
  • composition
  • hard to implement pooling
slide-28
SLIDE 28

third pass, implicits

slide-29
SLIDE 29

third pass, implicits

trait Exec[A] {

slide-30
SLIDE 30

third pass, implicits

trait Exec[A] { def map[B](f: A => B)(implicit c: Conn): Exec[B]

slide-31
SLIDE 31

third pass, implicits

trait Exec[A] { def map[B](f: A => B)(implicit c: Conn): Exec[B] def flatMap[B](f: A => Exec[B])(implicit c: Conn) : Exec[B] }

slide-32
SLIDE 32

third pass, implicits

slide-33
SLIDE 33

third pass, implicits

  • it’s Scala, implicits are the thing
slide-34
SLIDE 34

third pass, implicits

  • it’s Scala, implicits are the thing
  • requires
  • client code to manage the instance
  • often done poorly
slide-35
SLIDE 35

recap: requirements

slide-36
SLIDE 36

recap: requirements

  • execute code with an externally managed and

provided Connection (a program)

slide-37
SLIDE 37

recap: requirements

  • execute code with an externally managed and

provided Connection (a program)

  • compose small programs into larger programs
slide-38
SLIDE 38

recap: requirements

  • execute code with an externally managed and

provided Connection (a program)

  • compose small programs into larger programs
  • programs should not manage object lifecycles
slide-39
SLIDE 39

recap: requirements

  • execute code with an externally managed and

provided Connection (a program)

  • compose small programs into larger programs
  • programs should not manage object lifecycles
  • clients should not manage object lifecycles
slide-40
SLIDE 40

recap: requirements

  • execute code with an externally managed and

provided Connection (a program)

  • compose small programs into larger programs
  • programs should not manage object lifecycles
  • clients should not manage object lifecycles
  • provide central management
slide-41
SLIDE 41

so, let’s build some kit!

slide-42
SLIDE 42

so, let’s build some kit!

  • we’ll invest some time creating some library that

will suit our needs

slide-43
SLIDE 43

so, let’s build some kit!

  • we’ll invest some time creating some library that

will suit our needs

  • what would a functional programmer do?
slide-44
SLIDE 44
slide-45
SLIDE 45
  • f course it is!
slide-46
SLIDE 46
  • f course it is!

what we want is a Reader monad

slide-47
SLIDE 47

recap: Reader monad

slide-48
SLIDE 48

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) }

slide-49
SLIDE 49

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala

slide-50
SLIDE 50

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def point(a: A) =

slide-51
SLIDE 51

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def point(a: A) = new Reader[C, A](_ => a)

slide-52
SLIDE 52

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def point(a: A) = new Reader[C, A](_ => a) def map[B](f: A => B): Reader[C, B] =

slide-53
SLIDE 53

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def point(a: A) = new Reader[C, A](_ => a) def map[B](f: A => B): Reader[C, B] = new Reader[C, B](read andThen f)

slide-54
SLIDE 54

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def point(a: A) = new Reader[C, A](_ => a) def map[B](f: A => B): Reader[C, B] = new Reader[C, B](read andThen f) def flatMap[B](f: A => Reader[C, B]): Reader[C, B] =

slide-55
SLIDE 55

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def point(a: A) = new Reader[C, A](_ => a) def map[B](f: A => B): Reader[C, B] = new Reader[C, B](read andThen f) def flatMap[B](f: A => Reader[C, B]): Reader[C, B] = new Reader[C, B](c => f(read(c))(c)) }

slide-56
SLIDE 56

recap: Reader monad

slide-57
SLIDE 57

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def flatMap[B](f: A => Reader[C, B]): Reader[C, B] =

slide-58
SLIDE 58

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def flatMap[B](f: A => Reader[C, B]): Reader[C, B] = new Reader[C, B]({ c: C =>

slide-59
SLIDE 59

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def flatMap[B](f: A => Reader[C, B]): Reader[C, B] = new Reader[C, B]({ c: C => val a: A = read(c)

slide-60
SLIDE 60

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def flatMap[B](f: A => Reader[C, B]): Reader[C, B] = new Reader[C, B]({ c: C => val a: A = read(c) val readB: Reader[C, B] = f(a)

slide-61
SLIDE 61

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def flatMap[B](f: A => Reader[C, B]): Reader[C, B] = new Reader[C, B]({ c: C => val a: A = read(c) val readB: Reader[C, B] = f(a) val b: B = readB(c)

slide-62
SLIDE 62

recap: Reader monad

class Reader[C, A](f: C => A) extends (C => A) { def apply(c: C) = f(c) } class MonadReader[C, A](read: Reader[C, A]) extends Monad[Reader[C, _]] { // <- not really Scala def flatMap[B](f: A => Reader[C, B]): Reader[C, B] = new Reader[C, B]({ c: C => val a: A = read(c) val readB: Reader[C, B] = f(a) val b: B = readB(c) b }) }

slide-63
SLIDE 63

recap: Reader monad

slide-64
SLIDE 64

recap: Reader monad

  • the Monad on function application
  • f an environment
slide-65
SLIDE 65

recap: Reader monad

  • the Monad on function application
  • f an environment
  • sub-computations may be fed

an altered environment

slide-66
SLIDE 66

connections

slide-67
SLIDE 67

connections

  • ur environment is a Connection:
slide-68
SLIDE 68

connections

  • ur environment is a Connection:

type Program[+A] = Reader[Connection, A]

slide-69
SLIDE 69

programs

slide-70
SLIDE 70

programs

/** Apply the native function to the given arguments */ def native(name: String, args: Arg*): Program[REXP] = …

slide-71
SLIDE 71

programs

/** Apply the native function to the given arguments */ def native(name: String, args: Arg*): Program[REXP] = … def mean(x: Seq[Double]): Program[Double] = native("mean", x) flatMap toDouble

slide-72
SLIDE 72

programs

/** Apply the native function to the given arguments */ def native(name: String, args: Arg*): Program[REXP] = … def mean(x: Seq[Double]): Program[Double] = native("mean", x) flatMap toDouble def stdDev(x: Seq[Double]): Program[Double] = if (x.length < 2) const(0D) // < 2 datas will NaN else native("sd", x) flatMap toDouble

slide-73
SLIDE 73

programs

/** Apply the native function to the given arguments */ def native(name: String, args: Arg*): Program[REXP] = … def mean(x: Seq[Double]): Program[Double] = native("mean", x) flatMap toDouble def stdDev(x: Seq[Double]): Program[Double] = if (x.length < 2) const(0D) // < 2 datas will NaN else native("sd", x) flatMap toDouble def meanAndStdDev(values: Seq[Double]) : Program[(Double, Double, Int)] =

slide-74
SLIDE 74

programs

/** Apply the native function to the given arguments */ def native(name: String, args: Arg*): Program[REXP] = … def mean(x: Seq[Double]): Program[Double] = native("mean", x) flatMap toDouble def stdDev(x: Seq[Double]): Program[Double] = if (x.length < 2) const(0D) // < 2 datas will NaN else native("sd", x) flatMap toDouble def meanAndStdDev(values: Seq[Double]) : Program[(Double, Double, Int)] = for { m <- mean(values) sd <- stdDev(values) } yield (m, sd, values.length)

slide-75
SLIDE 75

now what?

slide-76
SLIDE 76

now what?

  • getting a value of type Program[A] is not the same

as getting a value of type A, a program still needs to be executed.

slide-77
SLIDE 77

now what?

  • getting a value of type Program[A] is not the same

as getting a value of type A, a program still needs to be executed.

  • how do we execute a program?
slide-78
SLIDE 78

now what?

  • getting a value of type Program[A] is not the same

as getting a value of type A, a program still needs to be executed.

  • how do we execute a program?
  • first we need to compile it
slide-79
SLIDE 79

compile

slide-80
SLIDE 80

compile

def compile[A](program: Program[A]): IO[A] =

slide-81
SLIDE 81

compile

def compile[A](program: Program[A]): IO[A] = IO {

slide-82
SLIDE 82

compile

def compile[A](program: Program[A]): IO[A] = IO { for { server <- Server.start()

slide-83
SLIDE 83

compile

def compile[A](program: Program[A]): IO[A] = IO { for { server <- Server.start() result <- try server execute program

slide-84
SLIDE 84

compile

def compile[A](program: Program[A]): IO[A] = IO { for { server <- Server.start() result <- try server execute program finally server.stop()

slide-85
SLIDE 85

compile

def compile[A](program: Program[A]): IO[A] = IO { for { server <- Server.start() result <- try server execute program finally server.stop() } yield result }

slide-86
SLIDE 86

compile

def compile[A](program: Program[A]): IO[A] = IO { for { server <- Server.start() result <- try server execute program finally server.stop() } yield result } def main(args: Array[String]) { batchedReduction.run.unsafePerformIO }

slide-87
SLIDE 87

pooled

slide-88
SLIDE 88

pooled

def compilePooled[A](program: Program[A]): IO[A] =

slide-89
SLIDE 89

pooled

def compilePooled[A](program: Program[A]): IO[A] = IO {

slide-90
SLIDE 90

pooled

def compilePooled[A](program: Program[A]): IO[A] = IO { for { server <- pool.borrow

slide-91
SLIDE 91

pooled

def compilePooled[A](program: Program[A]): IO[A] = IO { for { server <- pool.borrow result <- try server execute program finally pool + server

slide-92
SLIDE 92

pooled

def compilePooled[A](program: Program[A]): IO[A] = IO { for { server <- pool.borrow result <- try server execute program finally pool + server } yield result }

slide-93
SLIDE 93

problems

slide-94
SLIDE 94

problems

  • error handling
slide-95
SLIDE 95

problems

  • error handling
  • tail recursion/stack overflow
slide-96
SLIDE 96

error handling

slide-97
SLIDE 97

error handling

sealed trait Invalid

slide-98
SLIDE 98

error handling

sealed trait Invalid case class Message(s: String) extends Invalid

slide-99
SLIDE 99

error handling

sealed trait Invalid case class Message(s: String) extends Invalid case class Err(x: Throwable) extends Invalid { … // define custom eq as Throwable doesn’t define eq }

slide-100
SLIDE 100

error handling

sealed trait Invalid case class Message(s: String) extends Invalid case class Err(x: Throwable) extends Invalid { … // define custom eq as Throwable doesn’t define eq } case class Composite(left: Invalid, right: Invalid) extends Invalid

slide-101
SLIDE 101

error handling

sealed trait Invalid case class Message(s: String) extends Invalid case class Err(x: Throwable) extends Invalid { … // define custom eq as Throwable doesn’t define eq } case class Composite(left: Invalid, right: Invalid) extends Invalid // scalaz.\/ is a far better Either[Invalid, A]

slide-102
SLIDE 102

error handling

sealed trait Invalid case class Message(s: String) extends Invalid case class Err(x: Throwable) extends Invalid { … // define custom eq as Throwable doesn’t define eq } case class Composite(left: Invalid, right: Invalid) extends Invalid // scalaz.\/ is a far better Either[Invalid, A] type Result[+A] = Invalid \/ A

slide-103
SLIDE 103

tail recursion

slide-104
SLIDE 104

tail recursion

  • to turn a tail recursive program into a non-tail-

recursive one we need trampolining

slide-105
SLIDE 105

tail recursion

  • to turn a tail recursive program into a non-tail-

recursive one we need trampolining

  • essentially turns recursive function application into

continuation objects, replacing stack with heap

slide-106
SLIDE 106

tail recursion

  • to turn a tail recursive program into a non-tail-

recursive one we need trampolining

  • essentially turns recursive function application into

continuation objects, replacing stack with heap

  • fortunately there’s an excellent implementation of

this already built: scalaz.Free monad[1]

slide-107
SLIDE 107
  • n noes, too many Monads!
slide-108
SLIDE 108
  • n noes, too many Monads!
  • lots of Monads
  • Free
  • Result – Either or \/
  • Program
  • IO
slide-109
SLIDE 109
  • n noes, too many Monads!
  • lots of Monads
  • Free
  • Result – Either or \/
  • Program
  • IO
  • stack them together with MonadTransformers
slide-110
SLIDE 110

ReaderT

slide-111
SLIDE 111

ReaderT

type ReaderT[F[+_], -E, +A] = Kleisli[F, E, A]

slide-112
SLIDE 112

ReaderT

type ReaderT[F[+_], -E, +A] = Kleisli[F, E, A] /** Represents a function `A => M[B]`. */ sealed trait Kleisli[M[+_], -A, +B] { self =>

slide-113
SLIDE 113

ReaderT

type ReaderT[F[+_], -E, +A] = Kleisli[F, E, A] /** Represents a function `A => M[B]`. */ sealed trait Kleisli[M[+_], -A, +B] { self => def run(a: A): M[B]

slide-114
SLIDE 114

ReaderT

type ReaderT[F[+_], -E, +A] = Kleisli[F, E, A] /** Represents a function `A => M[B]`. */ sealed trait Kleisli[M[+_], -A, +B] { self => def run(a: A): M[B] def map[C](f: B => C)(implicit M: Functor[M]) : Kleisli[M, A, C] = kleisli(a => M.map(run(a))(f))

slide-115
SLIDE 115

ReaderT

type ReaderT[F[+_], -E, +A] = Kleisli[F, E, A] /** Represents a function `A => M[B]`. */ sealed trait Kleisli[M[+_], -A, +B] { self => def run(a: A): M[B] def map[C](f: B => C)(implicit M: Functor[M]) : Kleisli[M, A, C] = kleisli(a => M.map(run(a))(f)) /**Construct a Kleisli from a Function1 */

slide-116
SLIDE 116

ReaderT

type ReaderT[F[+_], -E, +A] = Kleisli[F, E, A] /** Represents a function `A => M[B]`. */ sealed trait Kleisli[M[+_], -A, +B] { self => def run(a: A): M[B] def map[C](f: B => C)(implicit M: Functor[M]) : Kleisli[M, A, C] = kleisli(a => M.map(run(a))(f)) /**Construct a Kleisli from a Function1 */ def kleisli[M[+_], A, B](f: A => M[B]) = new Kleisli[M, A, B] { def run(a: A) = f(a) } }

slide-117
SLIDE 117

monad stack

slide-118
SLIDE 118

monad stack

type Id[+X] = X

slide-119
SLIDE 119

monad stack

type Id[+X] = X type ResultT[F[+_], +A] = scalaz.EitherT[F, Invalid, A]

slide-120
SLIDE 120

monad stack

type Id[+X] = X type ResultT[F[+_], +A] = scalaz.EitherT[F, Invalid, A] type Result[+A] = ResultT[Id, A]

slide-121
SLIDE 121

monad stack

type Id[+X] = X type ResultT[F[+_], +A] = scalaz.EitherT[F, Invalid, A] type Result[+A] = ResultT[Id, A] // type alias for Free partially applied to Id type FreeId[+A] = Free[Id, A]

slide-122
SLIDE 122

monad stack

type Id[+X] = X type ResultT[F[+_], +A] = scalaz.EitherT[F, Invalid, A] type Result[+A] = ResultT[Id, A] // type alias for Free partially applied to Id type FreeId[+A] = Free[Id, A] type Connected[+A] = ReaderT[FreeId, Connection, A]

slide-123
SLIDE 123

monad stack

type Id[+X] = X type ResultT[F[+_], +A] = scalaz.EitherT[F, Invalid, A] type Result[+A] = ResultT[Id, A] // type alias for Free partially applied to Id type FreeId[+A] = Free[Id, A] type Connected[+A] = ReaderT[FreeId, Connection, A] type Program[+A] = ResultT[Connected, A]

slide-124
SLIDE 124

monad stack

type Id[+X] = X type ResultT[F[+_], +A] = scalaz.EitherT[F, Invalid, A] type Result[+A] = ResultT[Id, A] // type alias for Free partially applied to Id type FreeId[+A] = Free[Id, A] type Connected[+A] = ReaderT[FreeId, Connection, A] type Program[+A] = ResultT[Connected, A] type IOResult[+A] = ResultT[IO, A]

slide-125
SLIDE 125

which allows us to

slide-126
SLIDE 126

which allows us to

// remember this? def mean(x: Seq[Double]): Program[Double] = native("mean", x) flatMap toDouble

slide-127
SLIDE 127

which allows us to

// remember this? def mean(x: Seq[Double]): Program[Double] = native("mean", x) flatMap toDouble val toDouble: REXP => Program[Double] = rexp => { val d = rexp.asDouble if (d.isNaN) error("Computed NaN from REXP".invalid) else d }

slide-128
SLIDE 128

which allows us to

// remember this? def mean(x: Seq[Double]): Program[Double] = native("mean", x) flatMap toDouble val toDouble: REXP => Program[Double] = rexp => { val d = rexp.asDouble if (d.isNaN) error("Computed NaN from REXP".invalid) else d } type Program[+A] = EitherT[ReaderT[Free[Id,A], Connection,A], Invalid,A]

slide-129
SLIDE 129

and it looks like

slide-130
SLIDE 130

and it looks like

def incrementalStats(key: ActionKey) = { debug(key) for { current <- fetchBuild(key).toProgram _ = debug("Stats for" + key.toString) values = current.map { _.value } (mean, sd, samples) <- computeStats(values) correlation <- cor(values, current.map { _.testtime }) (ks, lastmeandiff) <- { for { previd <- catchingToResult { findBuilds(key.product, key.series).filter { getId(_) < getId(current.head.testid) }.maxBy { getId } }.leftMap { _ => "No previous build".invalid }.toProgram prev <- fetchBuild(ActionKey(key.product, key.series, previd, key.action)).toProgram stats <- computeLastStats(values, prev.map { _.value }) } yield stats }.leftMap { x => { debug(x); x } }.orElse { const(1D, 1D) } } yield (key.product, key.build, key.series, key.action, mean, sd, ks, lastmeandiff, samples, correlation) }
slide-131
SLIDE 131

why?

slide-132
SLIDE 132

why?

So, what was the point of all this? Why did we put things inside those pesky monads again?

slide-133
SLIDE 133

why?

So, what was the point of all this? Why did we put things inside those pesky monads again? Well, by putting everything inside very carefully labelled boxes we have been able to abstract and generalise away all the mess in dealing with IO & Rserv processes and compose programs using them into larger ones that all Do the Right Thing™ automatically.

slide-134
SLIDE 134

was it hard?

slide-135
SLIDE 135

was it hard?

yes!

slide-136
SLIDE 136

was it hard?

yes!

it took hours to get everything compiling,

slide-137
SLIDE 137

was it hard?

yes!

it took hours to get everything compiling, and quite a lot of research

slide-138
SLIDE 138

was it worth it?

slide-139
SLIDE 139

was it worth it?

after all those hours (days?) getting it all to compile, we ran it

slide-140
SLIDE 140

was it worth it?

after all those hours (days?) getting it all to compile, we ran it it worked perfectly first time

slide-141
SLIDE 141

was it worth it?

after all those hours (days?) getting it all to compile, we ran it it worked perfectly first time it worked so well, MPJ though it wasn’t running; he was used to it opening and closing connections all the time, executing our whole program used just the one

slide-142
SLIDE 142

was it worth it?

after all those hours (days?) getting it all to compile, we ran it it worked perfectly first time it worked so well, MPJ though it wasn’t running; he was used to it opening and closing connections all the time, executing our whole program used just the one and I got a trip to LambdaJam!

slide-143
SLIDE 143

Stackless Scala With Free Monads Runar Óli Bjarnason http://days2012.scala-lang.org/sites/days2012/files/bjarnason_trampolines.pdf Using the Free Monad to Avoid Stack Overflows in Scala Michael Peyton Jones http://termsandtruthconditions.herokuapp.com//blog/2013/03/16/free-monad/ Free: Interpreters and Little Languages Mark Hibberd http://mth.io/talks/free/ scalaz http://github.com/scalaz/scalaz/

references