val expected : obj

Full name: Index.expected
val failwith : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.failwith
val actual : obj

Full name: Index.actual
type Chemical = | Water

Full name: Index.Version1.Chemical
union case Chemical.Water: Chemical
type Drum =
  {Type: Chemical;
   Size: decimal;}

Full name: Index.Version1.Drum
Drum.Type: Chemical
Drum.Size: decimal
Multiple items
val decimal : value:'T -> decimal (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.decimal

--------------------
type decimal = System.Decimal

Full name: Microsoft.FSharp.Core.decimal

--------------------
type decimal<'Measure> = decimal

Full name: Microsoft.FSharp.Core.decimal<_>
type Container =
  {Capacity: decimal;
   Contents: Drum list;}

Full name: Index.Version1.Container
Container.Capacity: decimal
Container.Contents: Drum list
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
union case PositiveSize.PositiveSize: decimal -> PositiveSize

--------------------
type PositiveSize =
  private | PositiveSize of decimal
  static member Zero : PositiveSize
  static member ( + ) : PositiveSize * PositiveSize -> PositiveSize

Full name: Index.PositiveSize
val x : decimal
val y : decimal
static member PositiveSize.Zero : PositiveSize

Full name: Index.PositiveSize.Zero
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
type Chemical = | Water

Full name: Index.Chemical
type Drum =
  {Type: Chemical;
   Size: PositiveSize;}

Full name: Index.Drum
Drum.Size: PositiveSize
type Container =
  {Capacity: PositiveSize;
   Contents: Drum list;}

Full name: Index.Container
Container.Capacity: PositiveSize
type ContainerSpecification = Drum -> Container -> Container option

Full name: Index.ContainerSpecification
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
type Pack = Drum list -> Container list -> Container list option

Full name: Index.Pack
val container : Container

Full name: Index.container
val checkSpaceSpec : drum:Drum -> container:Container -> Container option

Full name: Index.checkSpaceSpec
val drum : Drum
val container : Container
val water : Drum

Full name: Index.water
val totalSize : container:Container -> PositiveSize

Full name: Index.totalSize
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val sumBy : projection:('T -> 'U) -> list:'T list -> 'U (requires member ( + ) and member get_Zero)

Full name: Microsoft.FSharp.Collections.List.sumBy
val x : Drum
val tnt : Drum

Full name: Index.tnt
val biologicalSample : Drum

Full name: Index.biologicalSample
val checkBiologicalSpec : ContainerSpecification

Full name: Index.checkBiologicalSpec
val expected : Container option

Full name: Index.expected
val spec : ('a -> 'a -> bool)
val x : 'a
val y : 'a
val TNT : 'a
val BiologicalSample : 'a
val forall : predicate:('T -> bool) -> list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.forall
val validate : drum:Drum -> container:Container -> Container option

Full name: Index.validate
val candidate : Container
val spec3 : drum:Drum -> container:Container -> Container option

Full name: Index.spec3
val spec4 : drum:Drum -> container:Container -> Container option

Full name: Index.spec4
val andThen : spec2:ContainerSpecification -> spec1:ContainerSpecification -> drum:Drum -> container:Container -> Container option

Full name: Index.andThen
val spec2 : ContainerSpecification
val spec1 : ContainerSpecification
val validate : ContainerSpecification

Full name: Index.validate
val validate : rules:ContainerSpecification list -> ContainerSpecification

Full name: Index.validate
val rules : ContainerSpecification list
val zero : ContainerSpecification
val fold : folder:('State -> 'T -> 'State) -> state:'State -> list:'T list -> 'State

Full name: Microsoft.FSharp.Collections.List.fold
val rules : ContainerSpecification list

Full name: Index.rules
val allRules : ContainerSpecification

Full name: Index.allRules

Cargo Kata specifications

"Find the best solution to pack drums into containers by respecting the following rules"

Bear with me by cloning : https://github.com/cboudereau/ContainerKata , and turn it into a hands-on if you prefer!

Rules

  • The container has a capacity limit and drums have different size
  • All chemicals in a container cannot exceed the capacity limit.
  • TNT drums require armored container
  • Ammonia drums require ventilated container
  • Biological samples and TNT should be separated.
  • Water and Biological samples do not require any container feature.

TDD

Let's start to write our first Unit Test...

1: 
2: 
3: 
4: 
let expected = failwith "not yet implemented"
let actual = failwith "not yet implemented"

actual |> should equal expected

How to describe types ?

"Design matters : how it is possible to represent actual behavior without a good expected design first ?"

Type Driven Development at the rescue!

Design in the REPL

  • What is a REPL ?
  • How to write test in the REPL ?

Domain Types

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
module Version1 = 
    type Chemical = Water

    type Drum = 
        { Type : Chemical
          Size : decimal }

    type Container = 
        { Capacity : decimal 
          Contents : Drum list }

Primitive obsession : Potential bugs !

  • Can the Size be negative ?
  • Can Size multiplied by Size matter ? ...

Make illegal states unrepresentable !

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
type PositiveSize = private PositiveSize of decimal
    with static member (+) (PositiveSize x, PositiveSize y) = PositiveSize (x + y)
         static member Zero = PositiveSize 0m
module PositiveSize = 
    let build x = if x >= 0m then Some (PositiveSize x) else None
    let get x = 
        build x 
        |> function Some x -> x | None -> failwith "expected a positive size"

type Chemical = Water

type Drum = 
    { Type : Chemical
      Size : PositiveSize }

type Container = 
    { Capacity : PositiveSize 
      Contents : Drum list }

module Container =
    let empty capacity = { Capacity=capacity; Contents = [] } 

Domain Services

1: 
2: 
type ContainerSpecification = Drum -> Container -> Container option
type Pack = Drum list -> Container list -> Container list option

And here is our first test!

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let container = 100m |> PositiveSize.get |> Container.empty

let checkSpaceSpec : ContainerSpecification = 
    fun drum container ->failwith "not yet implemented"

let water =
    { Type = Water
      Size = PositiveSize.get 10m }

container |> checkSpaceSpec water = Some { container with Contents = [water] }
container |> checkSpaceSpec { water with Size = PositiveSize.get 101m } = None

Small implementation

1: 
2: 
3: 
4: 
let totalSize container = container.Contents |> List.sumBy (fun x -> x.Size)
let checkSpaceSpec : ContainerSpecification = fun drum container ->
    if ((container |> totalSize) + drum.Size) > container.Capacity then None
    else Some container

Biological samples and TNT should be separated

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
type Chemical = Water | TNT | BiologicalSample
let tnt = { Type = TNT; Size = PositiveSize.get 1m }
let biologicalSample = { Type = BiologicalSample; Size = PositiveSize.get 1m }

let checkBiologicalSpec : ContainerSpecification = failwith "not yet implemented"

{ container with Contents = [ tnt ] } 
|> checkBiologicalSpec biologicalSample = None

{ container with Contents = [ biologicalSample ] } 
|> checkBiologicalSpec tnt = None

let expected = Some { container with Contents = [ biologicalSample ] }
container 
|> checkBiologicalSpec biologicalSample = expected
    

Small implementation

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let checkBiologicalSpec : ContainerSpecification =
    let spec x y = 
        match x, y with
        | TNT, BiologicalSample | BiologicalSample, TNT -> false
        | _ -> true
    fun drum container ->
        if container.Contents |> List.forall (fun x -> spec x.Type drum.Type) then
            Some container 
        else None

Composing both checkSpaceSpec and checkBiologicalSpec ?

Function Composition

lego

First version

1: 
2: 
3: 
4: 
let validate : ContainerSpecification = fun drum container ->
    match checkSpaceSpec drum container with
    | None -> None
    | Some candidate -> checkBiologicalSpec drum candidate

What if adding more and more rules ?

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let spec3 : ContainerSpecification = 
    fun drum container -> failwith "not yet implemented"
let spec4 : ContainerSpecification = 
    fun drum container -> failwith "not yet implemented"

let validate : ContainerSpecification = fun drum container ->
    match checkSpaceSpec drum container with
    | None -> None
    | Some candidate -> 
        match checkBiologicalSpec drum container with
        | None -> None
        | Some candidate -> 
            match spec3 drum candidate with
            | None -> None
            | Some candidate -> spec4 drum candidate

Too much Complexity!

How to reduce complexity ?

"and then" aka Kleisli composition

1: 
2: 
3: 
4: 
5: 
let andThen (spec2:ContainerSpecification) (spec1:ContainerSpecification) 
    : ContainerSpecification = fun drum container ->
    match spec1 drum container with
    | Some candidate -> spec2 drum candidate
    | None -> None

Rewrite it again

1: 
2: 
3: 
4: 
5: 
let validate = 
    checkSpaceSpec
    |> andThen checkBiologicalSpec
    |> andThen spec3
    |> andThen spec4

Kleisli / "and then" FISH operator

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let (>=>) spec1 spec2 = andThen spec2 spec1


let validate = 
    checkSpaceSpec
    >=> checkBiologicalSpec
    >=> spec3
    >=> spec4

List of rules

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
let validate rules : ContainerSpecification = 
    let zero : ContainerSpecification = fun _ container -> Some container
    rules |> List.fold andThen zero

let rules : ContainerSpecification list = 
    [ checkSpaceSpec; checkBiologicalSpec; spec3; spec4 ]

let allRules : ContainerSpecification = rules |> validate

SOLID PRINCIPLES

Function composition all the way down!

Full implementation demo

When using Property Based Testing ?

  • Is it hard to enumerate ?
  • What are properties ?
  • Is it easy to prove ?
  • It is easy to verify ?