analyzing functional programs
TRANSCRIPT
Analyzing Functional Programs
Dave Cleaver
November 18, 2017
Abstraction
2
Abstraction
• Separate the what from the how
3
Abstraction
• Separate the what from the how
• Utilize simpler implementations to test
4
Techniques
• Tagless Final
• Free Monad
5
Tagless Final
6
trait OrderRepositoryAlgebra[F[_]] {
def put(order: Order): F[Order]
def get(orderId: Long): F[Option[Order]]
def delete(orderId: Long): F[Option[Order]]
}
7
trait OrderRepositoryAlgebra[F[_]] {
def put(order: Order): F[Order]
def get(orderId: Long): F[Option[Order]]
def delete(orderId: Long): F[Option[Order]]
}
8
trait OrderRepositoryAlgebra[F[_]] {
def put(order: Order): F[Order]
def get(orderId: Long): F[Option[Order]]
def delete(orderId: Long): F[Option[Order]]
}
9
Using the Algebra
10
class OrderService[F[_]](orderRepo: OrderRepositoryAlgebra[F]) {
def placeOrder(order: Order): F[Order] = orderRepo.put(order)
def updateStatus(orderId: Long, status: OrderStatus)
(implicit M: Monad[F]): EitherT[F, OrderError, Order] =
for {
order <- EitherT.fromOptionF(orderRepo.get(orderId), OrderNotFound(orderId))
updated = order.copy(status = status)
_ <- EitherT.right[OrderError](orderRepo.put(updated))
} yield updated
}
11
class OrderService[F[_]](orderRepo: OrderRepositoryAlgebra[F]) {
def placeOrder(order: Order): F[Order] = orderRepo.put(order)
def updateStatus(orderId: Long, status: OrderStatus)
(implicit M: Monad[F]): EitherT[F, OrderError, Order] =
for {
order <- EitherT.fromOptionF(orderRepo.get(orderId), OrderNotFound(orderId))
updated = order.copy(status = status)
_ <- EitherT.right[OrderError](orderRepo.put(updated))
} yield updated
}
12
class OrderService[F[_]](orderRepo: OrderRepositoryAlgebra[F]) {
def placeOrder(order: Order): F[Order] = orderRepo.put(order)
def updateStatus(orderId: Long, status: OrderStatus)
(implicit M: Monad[F]): EitherT[F, OrderError, Order] =
for {
order <- EitherT.fromOptionF(orderRepo.get(orderId), OrderNotFound(orderId))
updated = order.copy(status = status)
_ <- EitherT.right[OrderError](orderRepo.put(updated))
} yield updated
}
13
Production Interpreter
class DoobieOrderRepositoryInterpreter[F[_]: Monad](val xa: Transactor[F])
extends OrderRepositoryAlgebra[F] {
…
def put(order: Order): F[Order] = {
val insert: ConnectionIO[Order] = for {
id <- sql”…”.update.withUniqueGeneratedKeys[Long]("ID")
} yield order.copy(id = Some(id))
insert.transact(xa)
}
def get(orderId: Long): F[Option[Order]] = …
def delete(orderId: Long): F[Option[Order]] = …
}
15
class DoobieOrderRepositoryInterpreter[F[_]: Monad](val xa: Transactor[F])
extends OrderRepositoryAlgebra[F] {
…
def put(order: Order): F[Order] = {
val insert: ConnectionIO[Order] = for {
id <- sql”…”.update.withUniqueGeneratedKeys[Long]("ID")
} yield order.copy(id = Some(id))
insert.transact(xa)
}
def get(orderId: Long): F[Option[Order]] = …
def delete(orderId: Long): F[Option[Order]] = …
}
16
class DoobieOrderRepositoryInterpreter[F[_]: Monad](val xa: Transactor[F])
extends OrderRepositoryAlgebra[F] {
…
def put(order: Order): F[Order] = {
val insert: ConnectionIO[Order] = for {
id <- sql”…”.update.withUniqueGeneratedKeys[Long]("ID")
} yield order.copy(id = Some(id))
insert.transact(xa)
}
def get(orderId: Long): F[Option[Order]] = …
def delete(orderId: Long): F[Option[Order]] = …
}
17
Test Interpreter
18
class OrderRepositoryInMemoryInterpreter[F[_]: Applicative]
extends OrderRepositoryAlgebra[F] {
private val cache = new TrieMap[Long, Order]
private val random = new Random
def put(order: Order): F[Order] = {
val toSave = if (order.id.isDefined) order
else order.copy(id = Some(random.nextLong))
toSave.id.foreach { cache.put(_, toSave) }
toSave.pure[F]
}
def get(orderId: Long): F[Option[Order]] =
cache.get(orderId).pure[F]
def delete(orderId: Long): F[Option[Order]] =
cache.remove(orderId).pure[F]
}
19
class OrderRepositoryInMemoryInterpreter[F[_]: Applicative]
extends OrderRepositoryAlgebra[F] {
private val cache = new TrieMap[Long, Order]
private val random = new Random
def put(order: Order): F[Order] = {
val toSave = if (order.id.isDefined) order
else order.copy(id = Some(random.nextLong))
toSave.id.foreach { cache.put(_, toSave) }
toSave.pure[F]
}
def get(orderId: Long): F[Option[Order]] =
cache.get(orderId).pure[F]
def delete(orderId: Long): F[Option[Order]] =
cache.remove(orderId).pure[F]
}
20
class OrderRepositoryInMemoryInterpreter[F[_]: Applicative]
extends OrderRepositoryAlgebra[F] {
private val cache = new TrieMap[Long, Order]
private val random = new Random
def put(order: Order): F[Order] = {
val toSave = if (order.id.isDefined) order
else order.copy(id = Some(random.nextLong))
toSave.id.foreach { cache.put(_, toSave) }
toSave.pure[F]
}
def get(orderId: Long): F[Option[Order]] =
cache.get(orderId).pure[F]
def delete(orderId: Long): F[Option[Order]] =
cache.remove(orderId).pure[F]
}
21
What can we do with that?
• Unit Testing
• Property-based Testing
22
Limits
• Known inputs
• Possible inputs
• Under normal conditions
23
Generating Programs
24
Generating Programs
• Derive possible outputs (Gen[Output])
25
Generating Programs
• Derive possible outputs (Gen[Output])
• From the inputs (CoGen[Input])
26
Generating Interpreter
27
class OrderRepositoryGeneratingInterpreter(response: Gen[Option[Order]], genId: Gen[Long])
(implicit longInput: Cogen[Long])
extends OrderRepositoryAlgebra[Gen] {
def put(order: Order): Gen[Order] =
for {
id <- order.id.map(Gen.const(_)).getOrElse(genId).map(Some(_))
order <- Gen.const(order.copy(id = id))
} yield order
def get(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
def delete(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
}
28
class OrderRepositoryGeneratingInterpreter(response: Gen[Option[Order]], genId: Gen[Long])
(implicit longInput: Cogen[Long])
extends OrderRepositoryAlgebra[Gen] {
def put(order: Order): Gen[Order] =
for {
id <- order.id.map(Gen.const(_)).getOrElse(genId).map(Some(_))
order <- Gen.const(order.copy(id = id))
} yield order
def get(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
def delete(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
}
29
class OrderRepositoryGeneratingInterpreter(response: Gen[Option[Order]], genId: Gen[Long])
(implicit longInput: Cogen[Long])
extends OrderRepositoryAlgebra[Gen] {
def put(order: Order): Gen[Order] =
for {
id <- order.id.map(Gen.const(_)).getOrElse(genId).map(Some(_))
order <- Gen.const(order.copy(id = id))
} yield order
def get(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
def delete(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
}
30
class OrderRepositoryGeneratingInterpreter(response: Gen[Option[Order]], genId: Gen[Long])
(implicit longInput: Cogen[Long])
extends OrderRepositoryAlgebra[Gen] {
def put(order: Order): Gen[Order] =
for {
id <- order.id.map(Gen.const(_)).getOrElse(genId).map(Some(_))
order <- Gen.const(order.copy(id = id))
} yield order
def get(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
def delete(orderId: Long): Gen[Option[Order]] =
longInput.cogen(orderId, response)
}
31
Tracing
32
sealed trait OrderRepositoryTrace
33
sealed trait OrderRepositoryTrace
case class TracePut(order: Order) extends OrderRepositoryTrace
case class TraceGet(orderId: Long) extends OrderRepositoryTrace
case class TraceDelete(orderId: Long) extends OrderRepositoryTrace
34
sealed trait OrderRepositoryTrace
case class TracePut(order: Order) extends OrderRepositoryTrace
case class TraceGet(orderId: Long) extends OrderRepositoryTrace
case class TraceDelete(orderId: Long) extends OrderRepositoryTrace
35
Tracing Interpreter
36
class OrderRepositoryTraceInterpreter[F[_]](wrapped: OrderRepositoryAlgebra[F])
(implicit M: Monad[F])
extends OrderRepositoryAlgebra[WriterT[F, List[OrderRepositoryTrace], ?]] {
def log[A](message: OrderRepositoryTrace)(g: F[A]): WriterT[F, List[OrderRepositoryTrace], A] =
for {
_ <- WriterT.tell[F, List[OrderRepositoryTrace]](List(message))
result <- WriterT.lift[F, List[OrderRepositoryTrace], A](g)
} yield result
def put(order: Order): WriterT[F, List[OrderRepositoryTrace], Order] =
log(TracePut(order)) { wrapped.put(order) }
def get(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceGet(orderId)) { wrapped.get(orderId) }
def delete(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceDelete(orderId)) { wrapped.delete(orderId) }
}
37
class OrderRepositoryTraceInterpreter[F[_]](wrapped: OrderRepositoryAlgebra[F])
(implicit M: Monad[F])
extends OrderRepositoryAlgebra[WriterT[F, List[OrderRepositoryTrace], ?]] {
def log[A](message: OrderRepositoryTrace)(g: F[A]): WriterT[F, List[OrderRepositoryTrace], A] =
for {
_ <- WriterT.tell[F, List[OrderRepositoryTrace]](List(message))
result <- WriterT.lift[F, List[OrderRepositoryTrace], A](g)
} yield result
def put(order: Order): WriterT[F, List[OrderRepositoryTrace], Order] =
log(TracePut(order)) { wrapped.put(order) }
def get(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceGet(orderId)) { wrapped.get(orderId) }
def delete(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceDelete(orderId)) { wrapped.delete(orderId) }
}
38
class OrderRepositoryTraceInterpreter[F[_]](wrapped: OrderRepositoryAlgebra[F])
(implicit M: Monad[F])
extends OrderRepositoryAlgebra[WriterT[F, List[OrderRepositoryTrace], ?]] {
def log[A](message: OrderRepositoryTrace)(g: F[A]): WriterT[F, List[OrderRepositoryTrace], A] =
for {
_ <- WriterT.tell[F, List[OrderRepositoryTrace]](List(message))
result <- WriterT.lift[F, List[OrderRepositoryTrace], A](g)
} yield result
def put(order: Order): WriterT[F, List[OrderRepositoryTrace], Order] =
log(TracePut(order)) { wrapped.put(order) }
def get(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceGet(orderId)) { wrapped.get(orderId) }
def delete(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceDelete(orderId)) { wrapped.delete(orderId) }
}
39
class OrderRepositoryTraceInterpreter[F[_]](wrapped: OrderRepositoryAlgebra[F])
(implicit M: Monad[F])
extends OrderRepositoryAlgebra[WriterT[F, List[OrderRepositoryTrace], ?]] {
def log[A](message: OrderRepositoryTrace)(g: F[A]): WriterT[F, List[OrderRepositoryTrace], A] =
for {
_ <- WriterT.tell[F, List[OrderRepositoryTrace]](List(message))
result <- WriterT.lift[F, List[OrderRepositoryTrace], A](g)
} yield result
def put(order: Order): WriterT[F, List[OrderRepositoryTrace], Order] =
log(TracePut(order)) { wrapped.put(order) }
def get(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceGet(orderId)) { wrapped.get(orderId) }
def delete(orderId: Long): WriterT[F, List[OrderRepositoryTrace], Option[Order]] =
log(TraceDelete(orderId)) { wrapped.delete(orderId) }
}
40
Code Properties
41
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
42
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
43
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
44
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
45
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
46
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
47
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
48
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
49
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
50
test("never delete when updating status") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryTraceInterpreter(orderGenInterpreter)
val orderService = OrderService(orderRepo)
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryTrace]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.written)
forAll { (walk: List[OrderRepositoryTrace]) =>
assert(!walk.exists {
case TraceDelete(_) => true
case _ => false })
}
}
51
List(TraceGet(5))
List(TraceGet(5))
List(TraceGet(5), TracePut(Order(4,Some(1969-12-31T19:00:00.011-05:00),Delivered,true,None)))
List(TraceGet(5))
List(TraceGet(5), TracePut(Order(27,Some(1969-12-31T19:00:00.007-05:00),Delivered,false,None)))
List(TraceGet(5), TracePut(Order(40,None,Delivered,false,Some(35))))
List(TraceGet(5))
List(TraceGet(5))
List(TraceGet(5))
List(TraceGet(5), TracePut(Order(38,None,Delivered,false,None)))
52
Free
• Encode our operations as classes
• Wrap in the Free Monad
53
Free Operations
54
sealed trait OrderRepositoryOp[A]
case class PutOp(order: Order) extends OrderRepositoryOp[Order]
case class GetOp(orderId: Long) extends OrderRepositoryOp[Option[Order]]
case class DeleteOp(orderId: Long) extends OrderRepositoryOp[Option[Order]]
55
sealed trait OrderRepositoryOp[A]
case class PutOp(order: Order) extends OrderRepositoryOp[Order]
case class GetOp(orderId: Long) extends OrderRepositoryOp[Option[Order]]
case class DeleteOp(orderId: Long) extends OrderRepositoryOp[Option[Order]]
56
sealed trait OrderRepositoryOp[A]
case class PutOp(order: Order) extends OrderRepositoryOp[Order]
case class GetOp(orderId: Long) extends OrderRepositoryOp[Option[Order]]
case class DeleteOp(orderId: Long) extends OrderRepositoryOp[Option[Order]]
57
Free Monad
58
class OrderRepositoryFree[F[_]](implicit I: InjectK[OrderRepositoryOp, F]) extends OrderRepositoryAlgebra[Free[F, ?]] {
def put(order: Order): Free[F, Order] = Free.inject[OrderRepositoryOp, F](PutOp(order))
def get(orderId: Long): Free[F, Option[Order]] =Free.inject[OrderRepositoryOp, F](GetOp(orderId))
def delete(orderId: Long): Free[F, Option[Order]] = Free.inject[OrderRepositoryOp, F](DeleteOp(orderId))
}
59
class OrderRepositoryFree[F[_]](implicit I: InjectK[OrderRepositoryOp, F])extends OrderRepositoryAlgebra[Free[F, ?]] {
def put(order: Order): Free[F, Order] = Free.inject[OrderRepositoryOp, F](PutOp(order))
def get(orderId: Long): Free[F, Option[Order]] =Free.inject[OrderRepositoryOp, F](GetOp(orderId))
def delete(orderId: Long): Free[F, Option[Order]] = Free.inject[OrderRepositoryOp, F](DeleteOp(orderId))
}
60
class OrderRepositoryFree[F[_]](implicit I: InjectK[OrderRepositoryOp, F]) extends OrderRepositoryAlgebra[Free[F, ?]] {
def put(order: Order): Free[F, Order] = Free.inject[OrderRepositoryOp, F](PutOp(order))
def get(orderId: Long): Free[F, Option[Order]] =Free.inject[OrderRepositoryOp, F](GetOp(orderId))
def delete(orderId: Long): Free[F, Option[Order]] = Free.inject[OrderRepositoryOp, F](DeleteOp(orderId))
}
61
class OrderRepositoryFree[F[_]](implicit I: InjectK[OrderRepositoryOp, F]) extends OrderRepositoryAlgebra[Free[F, ?]] {
def put(order: Order): Free[F, Order] = Free.inject[OrderRepositoryOp, F](PutOp(order))
def get(orderId: Long): Free[F, Option[Order]] =Free.inject[OrderRepositoryOp, F](GetOp(orderId))
def delete(orderId: Long): Free[F, Option[Order]] = Free.inject[OrderRepositoryOp, F](DeleteOp(orderId))
}
62
Generating Interpreter
63
class OrderRepositoryOpAsGen(genInterpreter: OrderRepositoryGeneratingInterpreter)
extends (OrderRepositoryOp ~> Gen) {
def apply[A](in: OrderRepositoryOp[A]): Gen[A] = in match {
case PutOp(order) => genInterpreter.put(order)
case GetOp(orderId) => genInterpreter.get(orderId)
case DeleteOp(orderId) => genInterpreter.delete(orderId)
}
}
64
class OrderRepositoryOpAsGen(genInterpreter: OrderRepositoryGeneratingInterpreter)
extends (OrderRepositoryOp ~> Gen) {
def apply[A](in: OrderRepositoryOp[A]): Gen[A] = in match {
case PutOp(order) => genInterpreter.put(order)
case GetOp(orderId) => genInterpreter.get(orderId)
case DeleteOp(orderId) => genInterpreter.delete(orderId)
}
}
65
class OrderRepositoryOpAsGen(genInterpreter: OrderRepositoryGeneratingInterpreter)
extends (OrderRepositoryOp ~> Gen) {
def apply[A](in: OrderRepositoryOp[A]): Gen[A] = in match {
case PutOp(order) => genInterpreter.put(order)
case GetOp(orderId) => genInterpreter.get(orderId)
case DeleteOp(orderId) => genInterpreter.delete(orderId)
}
}
66
class OrderRepositoryOpAsGen(genInterpreter: OrderRepositoryGeneratingInterpreter)
extends (OrderRepositoryOp ~> Gen) {
def apply[A](in: OrderRepositoryOp[A]): Gen[A] = in match {
case PutOp(order) => genInterpreter.put(order)
case GetOp(orderId) => genInterpreter.get(orderId)
case DeleteOp(orderId) => genInterpreter.delete(orderId)
}
}
67
class OrderRepositoryOpAsGen(genInterpreter: OrderRepositoryGeneratingInterpreter)
extends (OrderRepositoryOp ~> Gen) {
def apply[A](in: OrderRepositoryOp[A]): Gen[A] = in match {
case PutOp(order) => genInterpreter.put(order)
case GetOp(orderId) => genInterpreter.get(orderId)
case DeleteOp(orderId) => genInterpreter.delete(orderId)
}
}
68
Tracing Interpreter
69
class Trace[F[_], G[_]: Monad](nt: F ~> G)
extends (F ~> WriterT[G, List[F[_]], ?]) {
def apply[A](f: F[A]): WriterT[G, List[F[_]], A] =
for {
_ <- WriterT.tell[G, List[F[_]]](List(f))
a <- WriterT.lift[G, List[F[_]], A](nt(f))
} yield a
}
70
class Trace[F[_], G[_]: Monad](nt: F ~> G)
extends (F ~> WriterT[G, List[F[_]], ?]) {
def apply[A](f: F[A]): WriterT[G, List[F[_]], A] =
for {
_ <- WriterT.tell[G, List[F[_]]](List(f))
a <- WriterT.lift[G, List[F[_]], A](nt(f))
} yield a
}
71
class Trace[F[_], G[_]: Monad](nt: F ~> G)
extends (F ~> WriterT[G, List[F[_]], ?]) {
def apply[A](f: F[A]): WriterT[G, List[F[_]], A] =
for {
_ <- WriterT.tell[G, List[F[_]]](List(f))
a <- WriterT.lift[G, List[F[_]], A](nt(f))
} yield a
}
72
class Trace[F[_], G[_]: Monad](nt: F ~> G)
extends (F ~> WriterT[G, List[F[_]], ?]) {
def apply[A](f: F[A]): WriterT[G, List[F[_]], A] =
for {
_ <- WriterT.tell[G, List[F[_]]](List(f))
a <- WriterT.lift[G, List[F[_]], A](nt(f))
} yield a
}
73
class Trace[F[_], G[_]: Monad](nt: F ~> G)
extends (F ~> WriterT[G, List[F[_]], ?]) {
def apply[A](f: F[A]): WriterT[G, List[F[_]], A] =
for {
_ <- WriterT.tell[G, List[F[_]]](List(f))
a <- WriterT.lift[G, List[F[_]], A](nt(f))
} yield a
}
74
class Trace[F[_], G[_]: Monad](nt: F ~> G)
extends (F ~> WriterT[G, List[F[_]], ?]) {
def apply[A](f: F[A]): WriterT[G, List[F[_]], A] =
for {
_ <- WriterT.tell[G, List[F[_]]](List(f))
a <- WriterT.lift[G, List[F[_]], A](nt(f))
} yield a
}
75
Code Properties
76
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
77
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
78
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
79
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
80
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
81
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
82
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
83
test("never delete in update") {
val orderGenInterpreter =
new OrderRepositoryGeneratingInterpreter(Gen.option(order.arbitrary), Gen.posNum[Long])
val orderRepo = new OrderRepositoryFree[OrderRepositoryOp]
val orderService = OrderService(orderRepo)
val interpreter = new Trace(new OrderRepositoryOpAsGen(orderGenInterpreter))
implicit val arbitraryWalk: Arbitrary[List[OrderRepositoryOp[_]]] =
Arbitrary(orderService.updateStatus(5, Delivered).value.foldMap(interpreter).written)
forAll { (walk: List[OrderRepositoryOp[_]]) =>
assert(!walk.exists {
case DeleteOp(_) => true
case _ => false })
}
}
84
Analyzing Functional Programs
• Abstract
- Using Tagless Final
- or the Free Monad
• Test
- Not just inputs and outputs
- Exercise behavior from your dependencies
- Check what your code tries to do
• Take a look at
- The Scala Pet Store: https://github.com/pauljamescleary/scala-pet-store
- Example Code: https://github.com/ComcastSamples/scala-pet-store-analyzefp
• More..
- Find me on twitter and github: dscleaver
- We’re hiring: https://jobs.comcast.com/
85