Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Why Haskell?

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Favour Immutability

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

The Owl Problem

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Map, Filter and Reduce

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

The Type System Is Your Friend

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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 { ... }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Keep code with side effects in one place

Slide 20

Slide 20 text

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 ""]

Slide 21

Slide 21 text

Functions All The Way Down

Slide 22

Slide 22 text

· 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.

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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 }

Slide 27

Slide 27 text

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 } }

Slide 28

Slide 28 text

Do We Still Need OOP?

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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?

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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.

Slide 37

Slide 37 text

Unfamiliarity Can be Mistaken For Complexity.

Slide 38

Slide 38 text

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.

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

References

Slide 41

Slide 41 text

No content