Type Driven Development in Swift

Type Driven Development in Swift

Type Driven Development in Swift. Ideas for designing programs type-first and leveraging the power of the type system in the Swift programming language.

8162cb1dea89b7041ba5d67d1c5a01f2?s=128

Johannes Weiss

February 26, 2015
Tweet

Transcript

  1. TYPE DRIVEN DEVELOPMENT IN SWIFT @JOHANNESWEISS || JOHANNES@JWEISS.IO

  2. TDD?

  3. TYDD!

  4. 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
  5. MAKING THE ILLEGAL IMPOSSIBLE ! ALLOWING THE COMPILER TO HELP

    AS MUCH AS POSSIBLE
  6. 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)
  7. BETTER enum BookStatus { case Available case Lost case LentTo(Person)

    } ▸ Goal: Only meaningful values can be created.
  8. BAD EXAMPLE func contentsOfDirectoryAtPath(path : String) -> ([String]?, NSError?) {

    [...] } WHY BAD? ▸ illegal values (nil, nil) & ([...], NSError(...))
  9. BETTER! enum Result<T> { case Success(T) case Failure(NSError) } func

    contentsOfDirectoryAtPath(path : String) -> Result<[String]> func createDirectoryAtPath(path : String) -> Result<()> func dataWithJSONObject(obj : AnyObject) -> Result<NSData> * COMPILER BUGS CURRENTLY PREVENT THIS
  10. TEMPORARY WORKAROUND class Box<T> { let unbox : T init(_

    value: T) { self.unbox = value } } enum Result<T> { case Failure(NSError) case Success(Box<T>) init(Success value:T) { self = .Success(Box(value)) } init(Failure error:NSError) { self = .Failure(error) } }
  11. RESULT COALESCING OPERATOR func ??<T>(result: Result<T>, @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")
  12. MAPPING extension Result { func map<A>(f: T -> A) ->

    Result<A> { switch self { case .Success(let boxedValue): return Result<A>(Success: f(boxedValue.unbox)) case .Failure(let problem): return Result<A>(Failure: problem) } } } print(Result(Success:966).map { $0 / 42 }) /* Result(Success: 23) */
  13. WHEN DESIGNING TYPE FIRST, YOU OFTEN HAVE TO (TEMPORARILY) FILL

    HOLES ⛳️
  14. UNDEFINED CAN FILL HOLES func undefined<T>(_ 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())
  15. FUNCTION HOLES func parseIntFromString(input : String) -> Int? { let

    parsedInt = (undefined() as String -> Int?)(input) return parsedInt } func mayFail(x : Int) -> Result<Int> func addUpSuccesses(xs : [Int]) -> Int { let combine : (Int, Result<Int>) -> Int = undefined("fancy +") return map(xs, mayFail).reduce(0, combine:combine) } -> fatal error: undefined fancy +: file main.swift, line 214
  16. 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")) } }
  17. 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!") }
  18. ! PHANTOMS

  19. PHANTOM TYPES protocol Currency {} enum GBP : Currency {}

    enum EUR : Currency {} enum USD : Currency {} struct Money<C : Currency> { let amount : NSDecimalNumber }
  20. PHANTOM TYPE EXAMPLE func convertGBPtoEUR(gbp:Money<GBP>) -> Money<EUR> { let forex

    : NSDecimalNumber = NSDecimalNumber(mantissa: 133263, exponent: -5, isNegative: false) return Money(amount:gbp.amount.decimalNumberByMultiplyingBy(forex)) }
  21. THANK YOU! @JOHANNESWEISS || JOHANNES@JWEISS.IO || GITHUB.COM/WEISSI