Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

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
  2. summer :: Int -> Int -> Int summer a b

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

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

    -> a -> a ... -- Defined in ‘GHC.Num’ infixl 6 +
  5. ▸ Values don’t change under you. ▸ Easier to maintain

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

    where possible. ▸ Avoid mutating structs.
  7. ▸ Map a function over a list ▸ Write the

    rest of your marvellous program!
  8. 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: +)
  9. 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 } }
  10. MAPPING AND FILTERING IS MORE EXPRESSIVE let sumOfSquaresOfEvenNumbers = list

    .filter{ $0 % 2 == 0 } .map { $0 * $0 } .reduce(0, combine: +)
  11. 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
  12. ▸ Result is the context that has a value or

    an error in getting that value
  13. typealias is useful for making code clearer. func decode(json: [String

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

    the types and transformations of those types, before writing any code.
  16. func shuffle(deck: Deck) -> Deck { ... } func deal(deck:

    Deck, toPlayers: [Player]) -> [Hand] { ... } func winner(hands: [Hand]) -> Player { ... }
  17. 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 ""]
  18. ▸ 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.
  19. ▸ Aim to write small, pure functions with no side

    effects. ▸ Avoid mixing pure and impure code. ▸ Aim to write small, obviously correct functions.
  20. 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)) } }
  21. 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 } }
  22. 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 }
  23. 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 } }
  24. "Why are methods of protocols defined outside the Type?" struct

    SomeType: Equatable { ... } func ==(lhs: ...
  25. Have you ever created a class with nothing but class

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

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

    set of functionality, could a protocol be defined to encompass that functionality instead?
  29. 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.
  30. 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.
  31. This stuff is hard. But compared with what we already

    do and what we aspire to do; it's perfectly within our grasp.