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

What Haskell Teaches Me About Writing Swift

What Haskell Teaches Me About Writing Swift

My talk from AltConf 2015

Abizer Nasir

June 10, 2015
Tweet

More Decks by Abizer Nasir

Other Decks in Programming

Transcript

  1. WHAT HASKELL TEACHES ME ABOUT WRITING SWIFT
    Abizer Nasir | @abizern | abizern.org
    Swift Contractor - Shutl

    View Slide

  2. TL;DL
    LEARNING HASKELL WILL MAKE YOU A
    BETTER SWIFT PROGRAMMER.

    View Slide

  3. WHY HASKELL?

    View Slide

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

    View Slide

  5. TYPE INFERENCE

    View Slide

  6. summer :: Int -> Int -> Int
    summer a b = a + b
    λ> :t summer
    summer :: Int -> Int -> Int

    View Slide

  7. --summer :: Int -> Int -> Int
    summer a b = a + b
    λ> :t summer
    summer :: Num a => a -> a -> a

    View Slide

  8. λ> :i +
    class Num a where
    (+) :: a -> a -> a
    ...
    -- Defined in ‘GHC.Num’
    infixl 6 +

    View Slide

  9. FAVOUR
    IMMUTABLE
    STATE

    View Slide

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

    View Slide

  11. ▸ Prefer let to var
    ▸ Prefer structs over classes where possible.
    ▸ Avoid mutating structs.

    View Slide

  12. LISTS

    View Slide

  13. View Slide

  14. ▸ Map a function over a list
    ▸ Write the rest of your marvellous program!

    View Slide

  15. Don’t iterate over a list

    View Slide

  16. HIGHER ORDER FUNCTIONS
    let list = [1, 2, 3, 4]
    let squares = list.map{$0 * $0}
    let evens = list.filter{$0 % 2 == 0}
    let sum = list.reduce(0, combine: +)

    View Slide

  17. REDUCE IS OFTEN ENOUGH
    let squares2 = list.reduce([Int]()) {
    var accum = $0
    accum.append($1 * $1)
    return accum
    }
    let evens2 = list.reduce([Int]()){
    if $1 % 2 == 0 {
    var accum = $0
    accum.append($1)
    return accum
    } else {
    return $0
    }
    }

    View Slide

  18. MAPPING AND FILTERING IS MORE EXPRESSIVE
    let sumOfSquaresOfEvenNumbers = list
    .filter{ $0 % 2 == 0 }
    .map { $0 * $0 }
    .reduce(0, combine: +)

    View Slide

  19. MONADS

    View Slide

  20. A monad is a monoid in the category of
    endofunctors, what’s the problem? --
    Philip Wadler1
    1 Not really – see http://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html

    View Slide

  21. A MONAD IS
    (SORT OF) LIKE A
    BURRITO

    View Slide

  22. A VALUE IN A
    CONTEXT

    View Slide

  23. ▸ Optional is a context that contains a value or the lack of a value

    View Slide

  24. ▸ A list is a context that contains values in an order

    View Slide

  25. ▸ Result is the context that has a value or an error in getting that
    value

    View Slide

  26. THE TYPE SYSTEM
    IS YOUR FRIEND

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

  32. TYPE DRIVEN DEVELOPMENT

    View Slide

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

    View Slide

  34. KEEP CODE WITH
    SIDE EFFECTS IN
    ONE PLACE

    View Slide

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

  36. FUNCTIONS ALL
    THE WAY DOWN

    View Slide

  37. ▸ 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 Slide

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

    View Slide

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

    View Slide

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

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

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

  43. DO WE STILL
    NEED OOP?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

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

    View Slide

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

    View Slide

  52. REFERENCES

    View Slide

  53. Learn You a Haskell For A Great Good
    http://learnyouahaskell.com

    View Slide

  54. Real World Haskell
    http://book.realworldhaskell.org

    View Slide

  55. Functional Programming in Swift
    http://www.objc.io/books/fpinswift/

    View Slide

  56. Maybe Haskell
    https://gumroad.com/l/maybe-haskell/

    View Slide

  57. LEARNING HASKELL WILL MAKE YOU A
    BETTER SWIFT PROGRAMMER

    View Slide

  58. LEARNING A PROGRAMMING LANGUAGE WILL
    MAKE YOU A BETTER PROGRAMMER

    View Slide

  59. WITH COCOA WE HAVE THE CHOICE OF TWO
    DIFFERENT APPROACHES TO THE SAME
    PROBLEM SPACE.

    View Slide

  60. UNFAMILIARITY
    CAN BE MISTAKEN FOR
    COMPLEXITY

    View Slide

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

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

    View Slide

  63. THANK YOU
    &
    HAVE A GREAT WEEK!
    ABIZER NASIR | @ABIZERN | ABIZERN.ORG

    View Slide