Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Johannes Weiss

February 26, 2015
Tweet

More Decks by Johannes Weiss

Other Decks in Technology

Transcript

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

    View full-size slide

  2. 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

    View full-size slide

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

    View full-size slide

  4. 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)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  7. 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

    View full-size slide

  8. 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) }
    }

    View full-size slide

  9. 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")

    View full-size slide

  10. MAPPING
    extension Result {
    func map(f: T -> A) -> Result {
    switch self {
    case .Success(let boxedValue):
    return Result(Success: f(boxedValue.unbox))
    case .Failure(let problem):
    return Result(Failure: problem)
    }
    }
    }
    print(Result(Success:966).map { $0 / 42 }) /* Result(Success: 23) */

    View full-size slide

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

    View full-size slide

  12. 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())

    View full-size slide

  13. 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

    View full-size slide

  14. 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"))
    }
    }

    View full-size slide

  15. 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!")
    }

    View full-size slide

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

    View full-size slide

  17. 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))
    }

    View full-size slide

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

    View full-size slide