Slide 1

Slide 1 text

TYPE DRIVEN DEVELOPMENT IN SWIFT @JOHANNESWEISS || [email protected]

Slide 2

Slide 2 text

TDD?

Slide 3

Slide 3 text

TYDD!

Slide 4

Slide 4 text

KEY STRATEGIES & BENEFITS ▸ laying out the types first ! rapid prototyping & easy refactoring ▸ preferring compiler proof to unit test ! correctness & quicker ▸ reducing side-effects ! enable (unit) testing & composability

Slide 5

Slide 5 text

MAKING THE ILLEGAL IMPOSSIBLE ! ALLOWING THE COMPILER TO HELP AS MUCH AS POSSIBLE

Slide 6

Slide 6 text

BAD EXAMPLE struct BookStatusBad { let available : Bool let lost : Bool let lentTo : Person? } illegal values: let ill1 = BookStatusBad(available: true, lost: true, lentTo: TimCook) let ill2 = BookStatusBad(available: true, lost: false, lentTo: TimCook) let ill3 = BookStatusBad(available: false, lost: false, lentTo: nil)

Slide 7

Slide 7 text

BETTER enum BookStatus { case Available case Lost case LentTo(Person) } ▸ Goal: Only meaningful values can be created.

Slide 8

Slide 8 text

BAD EXAMPLE func contentsOfDirectoryAtPath(path : String) -> ([String]?, NSError?) { [...] } WHY BAD? ▸ illegal values (nil, nil) & ([...], NSError(...))

Slide 9

Slide 9 text

BETTER! enum Result { case Success(T) case Failure(NSError) } func contentsOfDirectoryAtPath(path : String) -> Result<[String]> func createDirectoryAtPath(path : String) -> Result<()> func dataWithJSONObject(obj : AnyObject) -> Result * COMPILER BUGS CURRENTLY PREVENT THIS

Slide 10

Slide 10 text

TEMPORARY WORKAROUND class Box { let unbox : T init(_ value: T) { self.unbox = value } } enum Result { case Failure(NSError) case Success(Box) init(Success value:T) { self = .Success(Box(value)) } init(Failure error:NSError) { self = .Failure(error) } }

Slide 11

Slide 11 text

RESULT COALESCING OPERATOR func ??(result: Result, @autoclosure defaultValue: () -> T) -> T { switch result { case .Success(let boxedValue): return boxedValue.unbox case .Failure(let _): return defaultValue() } } print(Result(Success:"Hello ") ?? "foo") print(Result(Failure: someError) ?? "World!\n")

Slide 13

Slide 13 text

WHEN DESIGNING TYPE FIRST, YOU OFTEN HAVE TO (TEMPORARILY) FILL HOLES ⛳️

Slide 14

Slide 14 text

UNDEFINED CAN FILL HOLES func undefined(_ message:String="") -> T { fatalError("undefined \(message)") } Can be used for simple things like let a : String = undefined("special string") let b : Int = undefined("some int") let t = NSTimer(timeInterval: undefined(), target: undefined(), selector: undefined(), userInfo: undefined(), repeats: undefined())

Slide 15

Slide 15 text

FUNCTION HOLES func parseIntFromString(input : String) -> Int? { let parsedInt = (undefined() as String -> Int?)(input) return parsedInt } func mayFail(x : Int) -> Result func addUpSuccesses(xs : [Int]) -> Int { let combine : (Int, Result) -> Int = undefined("fancy +") return map(xs, mayFail).reduce(0, combine:combine) } -> fatal error: undefined fancy +: file main.swift, line 214

Slide 16

Slide 16 text

CONCENTRATING ON SUCCESS FIRST func rm(path:String) -> Result<()> { if NSFileManager.defaultManager().removeItemAtPath(path, error:undefined("NSError pointer")) { return Result(Success:()) } else { return Result(Failure:undefined("the NSError returned above")) } }

Slide 17

Slide 17 text

HANDLING THE IMPOSSIBLE func lexicographicallyLowestString(x:String, y:String, z:String) -> String { let xyz = [x, y, z] return xyz.sorted({ $0 < $1 }).first ?? undefined("the impossible happened!") }

Slide 18

Slide 18 text

! PHANTOMS

Slide 19

Slide 19 text

PHANTOM TYPES protocol Currency {} enum GBP : Currency {} enum EUR : Currency {} enum USD : Currency {} struct Money { let amount : NSDecimalNumber }

Slide 20

Slide 20 text

PHANTOM TYPE EXAMPLE func convertGBPtoEUR(gbp:Money) -> Money { let forex : NSDecimalNumber = NSDecimalNumber(mantissa: 133263, exponent: -5, isNegative: false) return Money(amount:gbp.amount.decimalNumberByMultiplyingBy(forex)) }

Slide 21

Slide 21 text

THANK YOU! @JOHANNESWEISS || [email protected] || GITHUB.COM/WEISSI