Go Lift

Go Lift

A talk that's secretly about Category Theory, delivered at dotGo 2017

76c1414a09f42ad83b2296a8cdcd11a9?s=128

John Cinnamond

November 06, 2017
Tweet

Transcript

  1. GO LIFT John Cinnamond dotGo 2017

  2. PART 1 Constant interruptions

  3. Connect to the server

  4. Connect to the server Send first command

  5. Connect to the server Send first command Wait for ok

  6. Connect to the server Send first command Wait for ok

    Send second command
  7. conn := net.Dial(”tcp”, ”server:9876”)

  8. None
  9. What if this goes wrong?

  10. conn, err := net.Dial(”tcp”, ”server:9876”)

  11. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) }
  12. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } conn.Write(command1)
  13. None
  14. What if this goes wrong?

  15. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } , err := conn.Write(command1)
  16. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } , err := conn.Write(command1) if err != nil { panic(err) }
  17. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } , err := conn.Write(command1) if err != nil { panic(err) } r := bufio.NewReader(conn) status := r.ReadString(’\n’)
  18. None
  19. . . . collaborate and listen

  20. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } , err := conn.Write(command1) if err != nil { panic(err) } r := bufio.NewReader(conn) status, err := r.ReadString(’\n’)
  21. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } , err := conn.Write(command1) if err != nil { panic(err) } r := bufio.NewReader(conn) status, err := r.ReadString(’\n’) if err != nil { panic(err) }
  22. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } , err := conn.Write(command1) if err != nil { panic(err) } r := bufio.NewReader(conn) status, err := r.ReadString(’\n’) if err != nil { panic(err) } if status == ”ok” { conn.Write(command2) }
  23. None
  24. . . . Hammer time

  25. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) } , err := conn.Write(command1) if err != nil { panic(err) } r := bufio.NewReader(conn) status, err := r.ReadString(’\n’) if err != nil { panic(err) } if status == ”ok” { , err := conn.Write(command2) if err != nil { panic(err) } }
  26. Handling errors is a good thing

  27. Errors shouldn’t get in the way

  28. conn := net.Dial(”tcp”, ”server:9876”) conn.Write(command1) r := bufio.NewReader(conn) status :=

    r.ReadString(’\n’) if status == ”ok” { conn.Write(command2) }
  29. conn := net.Dial(”tcp”, ”server:9876”) conn.Write(command1) r := bufio.NewReader(conn) status :=

    r.ReadString(’\n’) if status == ”ok” { conn.Write(command2) } if err != nil if err != nil if err != nil if err != nil if err != nil if err != nil if err != nil if err != nil if err != nil if err != nil if err != nil
  30. Just use Haskell

  31. PART 2 if err != nil

  32. None
  33. Errors are values http://blog.golang.org/errors-are-values

  34. conn.Write(command1)

  35. conn.Write(command1) Not really the network

  36. conn.Write(command1) Not really the network An abstraction

  37. conn.Write(command1) Not really the network An abstraction A value

  38. conn.Write(command1) Not really the network An abstraction A value .

    . . containing data about the connection
  39. conn.Write(command1) Not really the network An abstraction A value .

    . . containing data about the connection . . . and behaviour to use it
  40. A value . . . containing data about the connection

    . . . and behaviour to use it Determined by the type
  41. Let’s add to the behaviour of net.Conn to handle errors

  42. Let’s add to the behaviour of net.Conn to handle errors

    ⇒ create a new type
  43. type SafeConn struct { }

  44. type SafeConn struct { conn net.Conn }

  45. type SafeConn struct { conn net.Conn err error }

  46. func (c ∗SafeConn) write(b []byte) { }

  47. func (c ∗SafeConn) write(b []byte) { c.conn.Write(b) }

  48. Add error handling

  49. func (c ∗SafeConn) write(b []byte) { c.conn.Write(b) }

  50. func (c ∗SafeConn) write(b []byte) { , c.err := c.conn.Write(b)

    }
  51. func (c ∗SafeConn) write(b []byte) { if c.err != nil

    { return } , c.err := c.Write(b) }
  52. c := SafeConn{conn, nil}

  53. c := SafeConn{conn, nil} c.write(command1) c.write(command2)

  54. c := SafeConn{conn, nil} c.write(command1) // if this has an

    error c.write(command2)
  55. c := SafeConn{conn, nil} c.write(command1) // if this has an

    error c.write(command2) // then this does nothing
  56. c := SafeConn{conn, nil} c.write(command1) // if this has an

    error c.write(command2) // then this does nothing if c.err != nil { panic(”omg”) }
  57. Repeat for other errors

  58. conn, err := net.Dial(”tcp”, ”server:9876”) if err != nil {

    panic(err) }
  59. func safeDial(network, address string) SafeConn { }

  60. func safeDial(network, address string) SafeConn { conn, err := net.Dial(network,

    address) }
  61. func safeDial(network, address string) SafeConn { conn, err := net.Dial(network,

    address) return SafeConn{conn, err} }
  62. c := safeDial(”tcp”, ”server:9876”)

  63. c := safeDial(”tcp”, ”server:9876”) c.write(command1) c.write(command2)

  64. c := safeDial(”tcp”, ”server:9876”) c.write(command1) c.write(command2) if err != nil

    { panic(”never”) }
  65. c := safeDial(”tcp”, ”server:9876”) // if this fails c.write(command1) c.write(command2)

    if err != nil { panic(”gonna”) }
  66. c := safeDial(”tcp”, ”server:9876”) // if this fails c.write(command1) //

    then this does nothing c.write(command2) if err != nil { panic(”give”) }
  67. c := safeDial(”tcp”, ”server:9876”) // if this fails c.write(command1) //

    then this does nothing c.write(command2) // same for this if err != nil { panic(”you”) }
  68. We still handle the error

  69. . . . but the error handling doesn’t get in

    the way
  70. c := safeDial(”tcp”, ”server:9876”) c.write(command1) c.write(command2) if err != nil

    { panic(”up”) }
  71. But wait!

  72. We introduced a new abstraction

  73. Abstractions have costs

  74. c := safeDial(”tcp”, ”server:9876”) c.write(command1) // what happens if this

    fails c.write(command2) if err != nil { panic(err) }
  75. Some details are hidden

  76. . . . but this is nothing new

  77. We use abstractions all the time

  78. Is this abstraction appropriate?

  79. PART 3 Division

  80. Given 3 numbers (a, b, c) ⇒ a/b/c

  81. func divide(a, b, c int ) { answer := a

    / b / c fmt. Println (answer) }
  82. func divide(a, b, c int ) { answer := a

    / b / c fmt. Println (answer) } ... divide(100, 10, 2) // 5
  83. func divide(a, b, c int ) { answer := a

    / b / c fmt. Println (answer) } ... divide(100, 10, 0)
  84. func divide(a, b, c int ) { answer := a

    / b / c fmt. Println (answer) } ... divide(100, 10, 0) // panic: runtime error: integer divide by zero
  85. func divide(a, b, c int ) { answer := a

    / b / c fmt. Println (answer) } ... divide(100, 10, 0) // Can’t divide by zero
  86. Let’s solve this badly

  87. func divide(a, b, c int ) { if b ==

    0 || c == 0 { fmt. Printf (”Can’t divide by zero”) return } answer = a / b / c fmt. Println (answer) }
  88. func divide(a, b, c int ) { answer := a

    / b / c fmt. Println (answer) }
  89. func divide(a, b, c int ) { answer := a

    / b / c fmt. Println (answer) } if b == 0 if b == 0 return if c == 0 if c == 0 if b == 0 if c == 0 if b == 0 if b == 0 if c == 0 if c == 0 if b == 0 return return
  90. Is this like err != nil ?

  91. Create a new type

  92. Create a new type Wrap the initial value

  93. Create a new type Wrap the initial value Wrap the

    behaviour
  94. Create a new type Wrap the initial value Wrap the

    behaviour Wrap the conditionals
  95. Create a new type

  96. type Divideinator struct { answer int }

  97. Wrap the initial value

  98. d := Divideinator{a}

  99. Wrap the behaviour

  100. func (d ∗Divideinator) divide(x int ) { d.answer = d.answer

    / x }
  101. Wrap the conditional

  102. func (d ∗Divideinator) divide(x int ) { if x ==

    0 { d.isZero = true return } d.answer = d.answer / x }
  103. func (d Divideinator) String () string { if d.isZero {

    return fmt. Sprintf (”Can’t divide by zero”) } return fmt. Sprintf (”%d”, d.answer) }
  104. Put it all together

  105. func divide(a, b, c int ) { d := Divideinator{a}

    }
  106. func divide(a, b, c int ) { d := Divideinator{a}

    d.divide(b) }
  107. func divide(a, b, c int ) { d := Divideinator{a}

    d.divide(b) d.divide(c) }
  108. func divide(a, b, c int ) { d := Divideinator{a}

    d.divide(b) d.divide(c) fmt. Println (d) }
  109. func divide(a, b, c int ) { d := Divideinator{a}

    d.divide(b) d.divide(c) fmt. Println (d) } ... divide(100, 10, 2) // 5
  110. func divide(a, b, c int ) { d := Divideinator{a}

    d.divide(b) d.divide(c) fmt. Println (d) } ... divide(100, 0, 2) // Can’t divide by zero
  111. func divide(a, b, c int ) { d := Divideinator{a}

    d.divide(b) d.divide(c) fmt. Println (d) } ... divide(100, 10, 0) // Can’t divide by zero
  112. This is the same approach as error handling

  113. Create a new type

  114. Create a new type Wrap the initial value

  115. Create a new type Wrap the initial value Wrap the

    behaviour
  116. Create a new type Wrap the initial value Wrap the

    behaviour Wrap the conditional
  117. I want to really understand this

  118. Let’s look at the shape of the code

  119. D(100) D(10) D(5) 100 10 5 divide(10) divide(2) /10 /2

  120. D(100) D(10) D(5) 100 10 5 divide(10) divide(2) /10 /10

    ; /2 /2
  121. D(100) D(10) D(5) 100 10 5 divide(x) ; divide(y) divide(10)

    divide(2) /10 /0 /2 /0
  122. Lift the initial value into a new type

  123. D(100) D(10) D(5) 100 10 5 divide(x) ; divide(y) divide(10)

    divide(2) /10 /0 Divideinator /2 /0
  124. Lift the behaviour

  125. D(100) D(10) D(5) 100 10 5 divide(x) ; divide(y) divide(10)

    divide(2) /10 /0 Divideinator /2 /0
  126. D(100) D(10) D(5) 100 10 5 divide(x) ; divide(y) divide(10)

    divide(2) /10 /0 Divideinator /2 /0
  127. Lift the conditionals

  128. D(100) D( ) D( ) 100 10 5 divide(x) ;

    divide(y) divide(0) divide(0) /10 /0 Divideinator /2 /0
  129. D(100) D D 100 10 5 divide(x) ; divide(y) divide(x)

    divide(y) /10 /0 /2 /0
  130. D(100) D D 100 10 5 divide(x) ; divide(y) divide(x)

    divide(y) /10 /0 /2 /0
  131. SafeConn SafeConn SafeConn conn conn conn write() ; write() Write

    error Write error
  132. SafeConn SafeConn SafeConn conn conn conn write() ; write() Write

    error Write error
  133. SafeConn SafeConn SafeConn conn conn conn write() ; write() Write

    error Write error
  134. SafeConn SafeConn SafeConn conn conn conn write() ; write() Write

    error Write error
  135. SafeConn SafeConn SafeConn conn conn conn write() ; write()

  136. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f g
  137. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f g
  138. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f T g
  139. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f T g
  140. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f T g T
  141. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f T g T T
  142. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f T g T T T
  143. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f g
  144. Ta Tb Tc a b c Tf Tf() ; Tg()

    Tg f g
  145. Less math. More code.

  146. PART 4 Building a webapp

  147. http://doesgohavegenericsyet.com

  148. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    }
  149. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    checkAlreadyRegistered(email) }
  150. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    checkAlreadyRegistered(email) register (email) }
  151. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    checkAlreadyRegistered(email) register (email) sellEmailToRecruiters(email) }
  152. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    checkAlreadyRegistered(email) register (email) sellEmailToRecruiters(email) logRequest(r) }
  153. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    checkAlreadyRegistered(email) register (email) sellEmailToRecruiters(email) logRequest(r) fmt. Fprintln (w, ...) }
  154. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) if

    !validateEmail(email) { http .Error(w, ...) return } checkAlreadyRegistered(email) register (email) sellEmailToRecruiters(email) logRequest(r) fmt. Fprintln (w, ...) }
  155. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) if

    !validateEmail(email) { logRequest(”invalid email”, r) http .Error(w, ...) return } checkAlreadyRegistered(email) register (email) sellEmailToRecruiters(email) logRequest(r) fmt. Fprintln (w, ...) }
  156. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) if

    !validateEmail(email) { logRequest(”invalid email”, r) http .Error(w, ...) return } if alreadyRegistered(email) { logRequest(”already registered”, r) http .Error(w, ...) return } register (email) sellEmailToRecruiters(email) logRequest(r) fmt. Fprintln (w, ...) }
  157. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) if

    !validateEmail(email) { logRequest(”invalid email”, r) http .Error(w, ...) return } if alreadyRegistered(email) { sellEmailToRecruiters(email) logRequest(”already registered”, r) http .Error(w, ...) return } register (email) sellEmailToRecruiters(email) logRequest(r) fmt. Fprintln (w, ...) }
  158. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) if

    !validateEmail(email) { logRequest(”invalid email”, r) http .Error(w, ...) return } if alreadyRegistered(email) { sellEmailToRecruiters(email) logRequest(”already registered”, r) http .Error(w, ...) return } if err := register (email); err != nil { sellEmailToRecruiters(email) logRequest(”registration failed ”, r) http .Error(w, ...) return }
  159. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    checkAlreadyRegistered(email) register (email) sellEmailToRecruiters(email) logRequest(r) fmt. Fprintln (w, ...) }
  160. func signupHandler(w http.ResponseWriter, r ∗http.Request) { email := r.FormValue(”email”) validateEmail(email)

    checkAlreadyRegistered(email) register (email) sellEmailToRecruiters(email) logRequest(r) fmt. Fprintln (w, ...) } if err != nil if alreadyRegistered logRequest if err != nil if !validateEmail logRequest if alreadyRegistered if err != nil sellEmail logRequest
  161. Ta Tb Tc Td Te a b c d e

    λ Tf Tf() ; Tg() ; Th() ; Ti() Tg Th Ti f g h i
  162. Ta Tb Tc Td Te a b c d e

    λ Tf Tf() ; Tg() ; Th() ; Ti() Tg Th Ti f g h i
  163. Ta Tb Tc Td Te a b c d e

    λ Tf Tf() ; Tg() ; Th() ; Ti() Tg Th Ti f g h i
  164. We know how to do this

  165. Ta Tb Tc Td Te a b c d e

    λ Tf Tf() ; Tg() ; Th() ; Ti() Tg Th Ti f T g h i
  166. Ta Tb Tc Td Te a b c d e

    λ Tf Tf() ; Tg() ; Th() ; Ti() Tg Th Ti f T T g T h T i T
  167. Ta Tb Tc Td Te a b c d e

    λ Tf Tf() ; Tg() ; Th() ; Ti() Tg Th Ti f T T g T h T i T T T T T
  168. Create a new type Lift initial data Lift behaviour Lift

    control flow
  169. Create a new type

  170. type SignupRequest struct { }

  171. Lift initial data

  172. type SignupRequest struct { w http .ResponseWriter r ∗http.Request }

  173. Lift behaviour

  174. func (s ∗SignupRequest) validate() { if s.email == ”” ||

    ... { s.err = ” invalid email” } }
  175. func (s ∗SignupRequest) checkNewRegistration() { if existingEmails.Contain(s.email) { s.err =

    ”already registered” } }
  176. Lift control flow

  177. func (s ∗SignupRequest) checkNewRegistration() { if existingEmails.Contain(s.email) { s.err =

    ”already registered” } }
  178. func (s ∗SignupRequest) checkNewRegistration() { if s.err != nil {

    return } if existingEmails.Contain(s.email) { s.err = ”already registered” } }
  179. Compose the functions

  180. func signupHandler(w http.ResponseWriter, r ∗http.Request) { s := newSignupRequest(w, r)

    }
  181. func signupHandler(w http.ResponseWriter, r ∗http.Request) { s := newSignupRequest(w, r)

    s.validate () }
  182. func signupHandler(w http.ResponseWriter, r ∗http.Request) { s := newSignupRequest(w, r)

    s.validate () s.checkNewRegistration() }
  183. func signupHandler(w http.ResponseWriter, r ∗http.Request) { s := newSignupRequest(w, r)

    s.validate () s.checkNewRegistration() s. register () }
  184. func signupHandler(w http.ResponseWriter, r ∗http.Request) { s := newSignupRequest(w, r)

    s.validate () s.checkNewRegistration() s. register () s.sellEmailToRecruiter() }
  185. func signupHandler(w http.ResponseWriter, r ∗http.Request) { s := newSignupRequest(w, r)

    s.validate () s.checkNewRegistration() s. register () s.sellEmailToRecruiter() s.log() }
  186. func signupHandler(w http.ResponseWriter, r ∗http.Request) { s := newSignupRequest(w, r)

    s.validate () s.checkNewRegistration() s. register () s.sellEmailToRecruiter() s.log() s.respond() }
  187. Please don’t write your request handlers like this

  188. I’m not trying to tell you how to write code

  189. I’m trying to give you something new to think about

  190. Think about the shape of the code

  191. Think about using types

  192. I want to give you new tools

  193. . . . to cope with complex code

  194. It’s up to you to decide how to use them

  195. Epilogue

  196. We solve problems by breaking them into smaller pieces

  197. . . . but then we need to join the

    pieces back together again
  198. We need to understand the forces at play

  199. Mathematics gives us this understanding

  200. Mathematics lets us achieve more

  201. Mathematics is pretty useful

  202. Thank you John Cinnamond dotGo 2017 Gophers adapted from The

    Go Gopher by Renee French https://blog.golang.org/gopher