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

What Haskell Taught Me About Writing Swift

What Haskell Taught Me About Writing Swift

The slides from my talk at the Swift Summit conference in London on March 21st, 2015

Abizer Nasir

March 21, 2015
Tweet

More Decks by Abizer Nasir

Other Decks in Programming

Transcript

  1. What Haskell Taught Me About Writing swift
    Abizer Nasir | @abizern | abizern.org
    Swi! Contractor - Shutl

    View full-size slide

  2. Why Haskell?

    View full-size slide

  3. A polymorphically statically
    typed, lazy, purely functional
    language.
    — The Haskell Wiki

    View full-size slide

  4. Favour Immutability

    View full-size slide

  5. · Values don’t change under you.
    · Easier to maintain the code.
    · More likely to be thread safe.

    View full-size slide

  6. · Favour structs over classes where possible.
    · Avoid mutating structs.

    View full-size slide

  7. The Owl Problem

    View full-size slide

  8. · Map a function over a list
    · Write the rest of your marvellous program!

    View full-size slide

  9. Map, Filter and Reduce

    View full-size slide

  10. Using map, filter and reduce can lead to more
    expressive code
    let youngestManOver40 = population
    .filter { $0.age > 40 }
    .reduce(41) { min }

    View full-size slide

  11. The Type System Is Your
    Friend

    View full-size slide

  12. There is a whole class of bugs we no longer have
    to worry about.

    View full-size slide

  13. "If it compiles, you're most of the way to your
    solution"

    View full-size slide

  14. typealias is useful for making code clearer.
    func decode(json: [String : AnyObject]) -> MyType { ... }
    or
    typealias JSONDictionary = [String : AnyObject]
    func decode(json: JSONDictionary) -> myType { ... }

    View full-size slide

  15. It can make higher order function declarations
    easier to read.
    // Taken from AlamoFire
    typealias JSONResponseHandler = (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> ()
    // Define a common completion block with specific handler
    func responseHandlerWithCompletion(completion: Result) -> JSONResponseHandler { ... }

    View full-size slide

  16. · You can reason about your problem in terms of
    the types and transformations of those types,
    before writing any code.

    View full-size slide

  17. func shuffle(deck: Deck) -> Deck { ... }
    func deal(deck: Deck, toPlayers: [Player]) -> [Hand] { ... }
    func winner(hands: [Hand]) -> Player { ... }

    View full-size slide

  18. Keep code with side effects
    in one place

    View full-size slide

  19. module Main where
    import Control.Monad
    import Numeric
    factoryTimes :: Double -> Double -> [Double]
    factoryTimes c f = 0.0 : [ c / (2.0 + k * f) | k <- [0.0, 1.0 ..]]
    productionTimes :: Double -> Double -> [Double]
    productionTimes x f = [ x / (2.0 + k * f) | k <- [0.0, 1.0 ..]]
    times :: Double -> Double -> Double -> [Double]
    times c f x = zipWith (+) production factory
    where production = productionTimes x f
    factory = scanl1 (+) $ factoryTimes c f
    firstMinimum :: [Double] -> Double
    firstMinimum (x:y:ys) = if x < y
    then x
    else firstMinimum (y:ys)
    solve :: Double -> Double -> Double -> Double
    solve c f x = firstMinimum $ times c f x
    main :: IO ()
    main = do
    t <- getLine
    forM_ [1..read t :: Int] $ \i -> do
    [c, f, x] <- fmap (map read . words) getLine
    let result = solve c f x
    putStrLn $ concat ["Case #", show i, ": ", Numeric.showFFloat (Just 7) result ""]

    View full-size slide

  20. Functions All The Way
    Down

    View full-size slide

  21. · Don't have state, have functions that return
    values; although this is easier said than done.
    · Higher order functions can be used to reduce
    duplication in code.

    View full-size slide

  22. · Aim to write small, pure functions with no side
    effects.
    · Avoid mixing pure and impure code.
    · Aim to write small, obviously correct functions.

    View full-size slide

  23. Composition
    infix operator >>> { associativity right precedence 170 }
    func >>> (f: B -> C, g: A -> B) -> A -> C {
    return { x in f(g(x)) }
    }

    View full-size slide

  24. Map on Optionals
    infix operator <^> { associativity left }
    func <^> (f: A -> B, a: A?) -> B? {
    if let x = a {
    return f(x)
    } else {
    return nil
    }
    }

    View full-size slide

  25. Applicative on Optionals
    infix operator <*> { associativity left }
    func <*> (lhs: (A -> B)?, rhs: A?) -> B? {
    if let f = lhs {
    if let x = rhs {
    return f(x)
    }
    }
    return nil
    }

    View full-size slide

  26. Bind on Optionals
    infix operator >>== { associativity left precedence 150 }
    func >>== (a:A?, f: A -> B?) -> B? {
    if let x = a {
    return f(x)
    } else {
    return nil
    }
    }

    View full-size slide

  27. Do We Still Need OOP?

    View full-size slide

  28. OOP concerns data objects and
    operations on those data objects
    — My naive definition

    View full-size slide

  29. "Why are methods of protocols defined outside
    the Type?"
    struct SomeType: Equatable { ... }
    func ==(lhs: ...

    View full-size slide

  30. If we are supposed to favour immutability,
    shouldn't we be moving away from classes?

    View full-size slide

  31. Have you ever created a class with nothing but
    class functions just to keep related code
    together?

    View full-size slide

  32. Have you ever carefully exposed only the
    methods that were safe to call on your objects,
    keeping other methods private?

    View full-size slide

  33. If you have pure functions (no side effects, same
    inputs, same outputs) is it worth worrying about
    their access modifiers?
    Can you make them bare functions instead?

    View full-size slide

  34. If the functions can be grouped together into a
    logical set of functionality, could a protocol be
    defined to encompass that functionality instead?

    View full-size slide

  35. Programming to an interface was a
    recommendation even in the times of Objective-
    C. The stronger type system in Swi! makes it
    even more attractive.

    View full-size slide

  36. Unfamiliarity Can be
    Mistaken For Complexity.

    View full-size slide

  37. I want to write code like this:
    static func decode(object: JSON) -> DateWithTimeZone? {
    return jsonDictionary(object) >>== { dict in
    DateWithTimeZone.create <^>
    dict[dateWithTimeFormatKey(.Date)] >>== jsonDate <*>
    dict[dateWithTimeFormatKey(.TimeZone)] >>== jsonTimeZone
    }
    }
    It looks complex because it's unfamiliar, but it's
    clearer than having a pyramid of doom.

    View full-size slide

  38. This stuff is hard. But compared with what we
    already do and what we aspire to do; it's
    perfectly within our grasp.

    View full-size slide