Save 37% off PRO during our Black Friday Sale! »

Once More, with Types

Ed9a0d8cd44b62539b141f6c10405db1?s=47 Rob Napier
November 15, 2016

Once More, with Types

Functional programming in Swift is all the rage, but many of the tools of functional languages don't translate easily to Swift. So much focus on lambdas and monads has caused us to overlook one of the most powerful lessons from decades of functional programming research: the power of strong types.

In this session we'll discuss what makes types weak or strong and how to build simple types that make whole classes of bugs impossible. We'll learn to tear over-complicated types apart and put them back together using simple rules. And we'll learn the basic vocabulary of types so we can talk about why to choose a struct or an enum, when to use an Optional, and how to fix common type mistakes in Swift.

Ed9a0d8cd44b62539b141f6c10405db1?s=128

Rob Napier

November 15, 2016
Tweet

Transcript

  1. 1 @cocoaphony DO iOS 2016

  2. 2

  3. 3 struct Talk { let name = "DO iOS" let

    timeInterval: TimeInterval = 1479204000 var date: Date { return Date(timeIntervalSince1970: timeInterval) } } let name: String = "DO iOS" let timeInterval: TimeInterval = 1479204000
  4. 4

  5. 5 Preaching to the choir Voor eigen parochie preken

  6. 6 UIViewController

  7. 7 class MyViewController: UIViewController,
 UITableViewDataSource,
 UITableViewDelegate, 
 UISearchResultsUpdating, 
 UISearchControllerDelegate,

    
 UISearchBarDelegate, 
 MyViewModelDelegate, 
 OtherViewControllerDelegate
  8. 8

  9. 9 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->

    Int { if loggedIn { return values.count } else { return 1 } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if loggedIn { return normalCell(. . .) } else { return pleaseLoginCell(. . .) } } UITableViewController
  10. 10 override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->

    Int { if loggedIn { if searching { return searchResults.count } else { return values.count } } else { return 1 } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if loggedIn { if searching { return normalCell(...but with search results...) } else { return normalCell(. . .) } } else { return pleaseLoginCell(. . .) } } UITableViewController
  11. 11

  12. 12

  13. 13 class BrowsingDataSource: NSObject, NSTableViewDataSource class LoginDataSource: NSObject, NSTableViewDataSource class

    SearchingDataSource: NSObject, NSTableViewDataSource
  14. 14 func configureDataSource() { if loggedIn { if searching {

    dataSource = SearchingDataSource(. . .) } else { dataSource = BrowsingDataSource(. . .) } } else { dataSource = LoginDataSource(. . .) } tableView.dataSource = dataSource }
  15. 15 class BrowsingDataSource: NSObject, NSTableViewDataSource class SearchingDataSource: NSObject, NSTableViewDataSource class

    LoginDataSource: NSObject, NSTableViewDataSource
  16. 16 class FetchRequestDataSource: NSObject, NSTableViewDataSource class LoginDataSource: NSObject, NSTableViewDataSource

  17. 17 class StaticCellDataSource: NSObject, NSTableViewDataSource class FetchRequestDataSource: NSObject, NSTableViewDataSource

  18. 18 class ArrayDataSource<Element>: NSObject, UITableViewDataSource { let array: [Element] let

    identifier: String let configureCell: (UITableViewCell, Element) -> Void init(array: [Element], identifier: String, configureCell: @escaping (UITableViewCell, Element) -> Void) { self.array = array self.identifier = identifier self.configureCell = configureCell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return array.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) configureCell(cell, array[indexPath.row]) return cell } }
  19. 19

  20. 20 var names = ["Bob", "Charlie", "Alice"] var ages =

    [43, 22, 34] How do I sort names according to ages? Alice Bob Charlie 34 43 22
  21. 21 How do I sort names according to ages? Easy!

    func unzip<Element1, Element2>(_ zipped: [(Element1, Element2)]) -> ([Element1], [Element2]) { var xs: [Element1] = [] var ys: [Element2] = [] for (x, y) in zipped { xs.append(x) ys.append(y) } return (xs, ys) } (names, ages) = unzip(zip(names, ages) .sorted { $0.0 < $1.0 }) var names = ["Bob", "Charlie", "Alice"] var ages = [43, 22, 34]
  22. 22 Oops, forgot to update this code when I added

    a new array. Why is ages not the same length as names? How’d that happen? $0.1, $0.2, $1.3 … which one is the name again? displayPerson(names[i], ages[i], userids[i], addresses[i], managers[i], datesOfHire[i], state[1], . . .) extension Array { mutating func sort<T: Comparable>(byOther: [T]){ self = unzip(zip(self, byOther) .sorted { $0.1 < $1.1 }) .0 } } Easy? names.sort ages.sort(byOther: names) userids.sort(byOther: names) addresses.sort(byOther: names) managers.sort(byOther: names) . . .
  23. 23

  24. 24

  25. 25 struct Person { let name: String let age: Int

    } var persons = [Person(name: "Alice", age: 43), Person(name: "Bob", age: 34), Person(name: "Charlie", age: 22), ] persons.sort { $0.age < $1.age } Simple!
  26. 26

  27. 27 Public domain, original by J. Howard Miller

  28. 28

  29. 29 Every value this 
 type can hold

  30. 30 Every value this 
 type can hold Every value

    this
 type can hold
 in this program
  31. 31

  32. 32 Invariants Arrays have same number of elements Arrays in

    a consistent order var names = ["Bob", "Charlie", "Alice"] var ages = [43, 22, 34] var persons = [Person(name: "Alice", age: 43), Person(name: "Bob", age: 34), Person(name: "Charlie", age: 22), ] Test all possible code paths to ensure invariants are true. Impossible to write code such that invariants wouldn’t be true.
  33. 33 with thanks to Tony Hoare

  34. 34 Any(Object)

  35. 35

  36. 36 ()

  37. 37 Void

  38. 38 public enum Never {}

  39. 39 func fatalError(...) -> Void @noreturn

  40. 40 func fatalError(...) -> Never

  41. 41 Dictionary var book: [String: String] = [:]
 book["name"] =

    "Me"
 book["title"] = "Champion of Types" struct Book {
 var name: String
 var title: String
 }
  42. 42 Stringly Typed switch kind { case "start": print("Start") case

    "end": print("Done") default: fatalError("This shouldn't happen") } enum Kind { case start case end }
  43. 43 Int struct Person { let id: Int let name:

    String }
  44. 44 Int struct Person { struct ID { let value:

    Int } let id: ID let name: String }
  45. 45 struct ID { let value: Int }

  46. None
  47. 47 struct ImageCache { private var cache: [String: UIImage] =

    [:] }
  48. 48

  49. 49 func fetch(completion: (String?, String?, Error?) -> Void) {} (String?,

    String?, Error?)
  50. 50 (_ name: String?, _ address: String?, _ error: Error?)

    (String?, String?, Error?)
  51. 51 struct Style { let color: Color let isThick: Bool

    }
  52. 52 struct Person { let name: String let address: String

    } (Person?, Error?) set nil set ⁇ ✅ nil ✅ ⁇ person error (String?, String?, Error?)
  53. 53 enum Shape { case line case square(color: Color) }

  54. 54 enum Result<Value> { case success(Value) case failure(Error) }

  55. 55 func fetch(completion: (Result<Person>) -> Void) {} func fetch(completion: (String?,

    String?, Error?) -> Void) {}
  56. 56

  57. 57

  58. 58 @cocoaphony http://robnapier.net