$30 off During Our Annual Pro Sale. View Details »

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