Techniques like the Free Monad and Tagless Final allow us to build algebras that abstract Monad choices from our business logic. This abstraction allows unit tests to be run in a simple synchronous Monad while production code can still take advantage of complex asynchronous Monads. What else can we do with this abstracted business logic? In this talk, I'll explore the use of the Gen Monad from Scalacheck to produce random walks over our business logic consisting of the steps taken in our abstract algebras.
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
34
35. 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
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]]
55
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]]
56
57. 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
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 })
}
}
77
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 })
}
}
78
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 })
}
}
79
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 })
}
}
80
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 })
}
}
81
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 })
}
}
82
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 })
}
}
83
84. 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
85. 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