What Haskell Teaches Me About Writing Swift

What Haskell Teaches Me About Writing Swift

My talk from AltConf 2015

F28c4835c9e2277b6e04b86574532a2d?s=128

Abizer Nasir

June 10, 2015
Tweet

Transcript

  1. WHAT HASKELL TEACHES ME ABOUT WRITING SWIFT Abizer Nasir |

    @abizern | abizern.org Swift Contractor - Shutl
  2. TL;DL LEARNING HASKELL WILL MAKE YOU A BETTER SWIFT PROGRAMMER.

  3. WHY HASKELL?

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

    Haskell Wiki
  5. TYPE INFERENCE

  6. summer :: Int -> Int -> Int summer a b

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

    = a + b λ> :t summer summer :: Num a => a -> a -> a
  8. λ> :i + class Num a where (+) :: a

    -> a -> a ... -- Defined in ‘GHC.Num’ infixl 6 +
  9. FAVOUR IMMUTABLE STATE

  10. ▸ Values don’t change under you. ▸ Easier to maintain

    the code. ▸ More likely to be thread safe.
  11. ▸ Prefer let to var ▸ Prefer structs over classes

    where possible. ▸ Avoid mutating structs.
  12. LISTS

  13. None
  14. ▸ Map a function over a list ▸ Write the

    rest of your marvellous program!
  15. Don’t iterate over a list

  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: +)
  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 } }
  18. MAPPING AND FILTERING IS MORE EXPRESSIVE let sumOfSquaresOfEvenNumbers = list

    .filter{ $0 % 2 == 0 } .map { $0 * $0 } .reduce(0, combine: +)
  19. MONADS

  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
  21. A MONAD IS (SORT OF) LIKE A BURRITO

  22. A VALUE IN A CONTEXT

  23. ▸ Optional is a context that contains a value or

    the lack of a value
  24. ▸ A list is a context that contains values in

    an order
  25. ▸ Result is the context that has a value or

    an error in getting that value
  26. THE TYPE SYSTEM IS YOUR FRIEND

  27. There is a whole class of bugs we no longer

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

    solution"
  29. typealias is useful for making code clearer. func decode(json: [String

    : AnyObject]) -> MyType { ... } or typealias JSONDictionary = [String : AnyObject] func decode(json: JSONDictionary) -> myType { ... }
  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<MyType>) -> JSONResponseHandler { ... }
  31. ▸ You can reason about your problem in terms of

    the types and transformations of those types, before writing any code.
  32. TYPE DRIVEN DEVELOPMENT

  33. func shuffle(deck: Deck) -> Deck { ... } func deal(deck:

    Deck, toPlayers: [Player]) -> [Hand] { ... } func winner(hands: [Hand]) -> Player { ... }
  34. KEEP CODE WITH SIDE EFFECTS IN ONE PLACE

  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 ""]
  36. FUNCTIONS ALL THE WAY DOWN

  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.
  38. ▸ Aim to write small, pure functions with no side

    effects. ▸ Avoid mixing pure and impure code. ▸ Aim to write small, obviously correct functions.
  39. Composition infix operator >>> { associativity right precedence 170 }

    func >>> <A, B, C>(f: B -> C, g: A -> B) -> A -> C { return { x in f(g(x)) } }
  40. Map on Optionals infix operator <^> { associativity left }

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

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

    150 } func >>== <A, B>(a:A?, f: A -> B?) -> B? { if let x = a { return f(x) } else { return nil } }
  43. DO WE STILL NEED OOP?

  44. OOP concerns data objects and operations on those data objects

    — My naive definition
  45. "Why are methods of protocols defined outside the Type?" struct

    SomeType: Equatable { ... } func ==(lhs: ...
  46. If we are supposed to favour immutability, shouldn't we be

    moving away from classes?
  47. Have you ever created a class with nothing but class

    functions just to keep related code together?
  48. Have you ever carefully exposed only the methods that were

    safe to call on your objects, keeping other methods private?
  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?
  50. If the functions can be grouped together into a logical

    set of functionality, could a protocol be defined to encompass that functionality instead?
  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.
  52. REFERENCES

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

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

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

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

  57. LEARNING HASKELL WILL MAKE YOU A BETTER SWIFT PROGRAMMER

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

  59. WITH COCOA WE HAVE THE CHOICE OF TWO DIFFERENT APPROACHES

    TO THE SAME PROBLEM SPACE.
  60. UNFAMILIARITY CAN BE MISTAKEN FOR COMPLEXITY

  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.
  62. This stuff is hard. But compared with what we already

    do and what we aspire to do; it's perfectly within our grasp.
  63. THANK YOU & HAVE A GREAT WEEK! ABIZER NASIR |

    @ABIZERN | ABIZERN.ORG