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. 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
  2. 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)
  3. BETTER enum BookStatus { case Available case Lost case LentTo(Person)

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

    [...] } WHY BAD? ▸ illegal values (nil, nil) & ([...], NSError(...))
  5. 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
  6. 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) } }
  7. 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")
  8. 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) */
  9. 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())
  10. 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
  11. 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")) } }
  12. 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!") }
  13. PHANTOM TYPES protocol Currency {} enum GBP : Currency {}

    enum EUR : Currency {} enum USD : Currency {} struct Money<C : Currency> { let amount : NSDecimalNumber }
  14. 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)) }