analyzing functional programs

85
Analyzing Functional Programs Dave Cleaver November 18, 2017

Upload: dave-cleaver

Post on 21-Jan-2018

281 views

Category:

Engineering


0 download

TRANSCRIPT

Page 1: Analyzing Functional Programs

Analyzing Functional Programs

Dave Cleaver

November 18, 2017

Page 2: Analyzing Functional Programs

Abstraction

2

Page 3: Analyzing Functional Programs

Abstraction

• Separate the what from the how

3

Page 4: Analyzing Functional Programs

Abstraction

• Separate the what from the how

• Utilize simpler implementations to test

4

Page 5: Analyzing Functional Programs

Techniques

• Tagless Final

• Free Monad

5

Page 6: Analyzing Functional Programs

Tagless Final

6

Page 7: Analyzing Functional Programs

trait OrderRepositoryAlgebra[F[_]] {

def put(order: Order): F[Order]

def get(orderId: Long): F[Option[Order]]

def delete(orderId: Long): F[Option[Order]]

}

7

Page 8: Analyzing Functional Programs

trait OrderRepositoryAlgebra[F[_]] {

def put(order: Order): F[Order]

def get(orderId: Long): F[Option[Order]]

def delete(orderId: Long): F[Option[Order]]

}

8

Page 9: Analyzing Functional Programs

trait OrderRepositoryAlgebra[F[_]] {

def put(order: Order): F[Order]

def get(orderId: Long): F[Option[Order]]

def delete(orderId: Long): F[Option[Order]]

}

9

Page 10: Analyzing Functional Programs

Using the Algebra

10

Page 11: Analyzing Functional Programs

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

Page 12: Analyzing Functional Programs

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

Page 13: Analyzing Functional Programs

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

Page 14: Analyzing Functional Programs

Production Interpreter

Page 15: Analyzing Functional Programs

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

Page 16: Analyzing Functional Programs

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

Page 17: Analyzing Functional Programs

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

Page 18: Analyzing Functional Programs

Test Interpreter

18

Page 19: Analyzing Functional Programs

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

Page 20: Analyzing Functional Programs

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

Page 21: Analyzing Functional Programs

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

Page 22: Analyzing Functional Programs

What can we do with that?

• Unit Testing

• Property-based Testing

22

Page 23: Analyzing Functional Programs

Limits

• Known inputs

• Possible inputs

• Under normal conditions

23

Page 24: Analyzing Functional Programs

Generating Programs

24

Page 25: Analyzing Functional Programs

Generating Programs

• Derive possible outputs (Gen[Output])

25

Page 26: Analyzing Functional Programs

Generating Programs

• Derive possible outputs (Gen[Output])

• From the inputs (CoGen[Input])

26

Page 27: Analyzing Functional Programs

Generating Interpreter

27

Page 28: Analyzing Functional Programs

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

Page 29: Analyzing Functional Programs

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

Page 30: Analyzing Functional Programs

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

Page 31: Analyzing Functional Programs

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

Page 32: Analyzing Functional Programs

Tracing

32

Page 33: Analyzing Functional Programs

sealed trait OrderRepositoryTrace

33

Page 34: Analyzing Functional Programs

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

Page 35: Analyzing Functional Programs

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

Page 36: Analyzing Functional Programs

Tracing Interpreter

36

Page 37: Analyzing Functional Programs

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

Page 38: Analyzing Functional Programs

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

Page 39: Analyzing Functional Programs

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

Page 40: Analyzing Functional Programs

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

Page 41: Analyzing Functional Programs

Code Properties

41

Page 42: Analyzing Functional Programs

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

Page 43: Analyzing Functional Programs

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

Page 44: Analyzing Functional Programs

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

Page 45: Analyzing Functional Programs

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

Page 46: Analyzing Functional Programs

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

Page 47: Analyzing Functional Programs

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

Page 48: Analyzing Functional Programs

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

Page 49: Analyzing Functional Programs

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

Page 50: Analyzing Functional Programs

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

Page 51: Analyzing Functional Programs

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

Page 52: Analyzing Functional Programs

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

Page 53: Analyzing Functional Programs

Free

• Encode our operations as classes

• Wrap in the Free Monad

53

Page 54: Analyzing Functional Programs

Free Operations

54

Page 55: Analyzing Functional Programs

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

Page 56: Analyzing Functional Programs

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

Page 57: Analyzing Functional Programs

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

Page 58: Analyzing Functional Programs

Free Monad

58

Page 59: Analyzing Functional Programs

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

Page 60: Analyzing Functional Programs

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

Page 61: Analyzing Functional Programs

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

Page 62: Analyzing Functional Programs

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

Page 63: Analyzing Functional Programs

Generating Interpreter

63

Page 64: Analyzing Functional Programs

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

Page 65: Analyzing Functional Programs

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

Page 66: Analyzing Functional Programs

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

Page 67: Analyzing Functional Programs

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

Page 68: Analyzing Functional Programs

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

Page 69: Analyzing Functional Programs

Tracing Interpreter

69

Page 70: Analyzing Functional Programs

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

Page 71: Analyzing Functional Programs

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

Page 72: Analyzing Functional Programs

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

Page 73: Analyzing Functional Programs

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

Page 74: Analyzing Functional Programs

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

Page 75: Analyzing Functional Programs

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

Page 76: Analyzing Functional Programs

Code Properties

76

Page 77: Analyzing Functional Programs

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

Page 78: Analyzing Functional Programs

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

Page 79: Analyzing Functional Programs

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

Page 80: Analyzing Functional Programs

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

Page 81: Analyzing Functional Programs

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

Page 82: Analyzing Functional Programs

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

Page 83: Analyzing Functional Programs

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

Page 84: Analyzing Functional Programs

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

Page 85: Analyzing Functional Programs

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