My adventure with Elm

9,226 views

Published on

In this talk I introduced Functional Reactive Programming and ELM and finished off with a live demo building a complete game of snake in Elm.
Code for the demo can be found at : https://github.com/theburningmonk/elm-snake

Published in: Technology

My adventure with Elm

  1. 1. My adventure with ELM SCORE 42 by Yan Cui
  2. 2. agenda
  3. 3. Hi, my name is Yan Cui.
  4. 4. I’m not an expert on Elm.
  5. 5. Function Reactive Programming?
  6. 6. Value over Time
  7. 7. Time Value
  8. 8. Time Value Signal
  9. 9. Move Up Move Down
  10. 10. private var arrowKeyUp:Bool;! private var arrowKeyDown:Bool;! ! private var platform1:Platform;! private var platform2:Platform;! private var ball:Ball;
  11. 11. function keyDown(event:KeyboardEvent):Void {! ! if (currentGameState == Paused &&! ! ! event.keyCode == 32) {! ! ! setGameState(Playing);! ! } else if (event.keyCode == 38) {! ! ! arrowKeyUp = true;! ! } else if (event.keyCode == 40) {! ! ! arrowKeyDown = true;! ! }! }
  12. 12. function keyUp(event:KeyboardEvent):Void {! ! if (event.keyCode == 38) {! ! ! arrowKeyUp = false;! ! } else if (event.keyCode == 40) {! ! ! arrowKeyDown = false;! ! }! }
  13. 13. function everyFrame(event:Event):Void {! ! if(currentGameState == Playing){! ! ! if (arrowKeyUp) {! ! ! ! platform1.y -= platformSpeed;! ! ! }! ! ! if (arrowKeyDown) {! ! ! ! platform1.y += platformSpeed;! ! ! }! ! ! if (platform1.y < 5) platform1.y = 5;! ! ! if (platform1.y > 395) platform1.y = 395;! ! }! }
  14. 14. function everyFrame(event:Event):Void {! ! if(currentGameState == Playing){! ! ! if (arrowKeyUp) {! ! ! ! platform1.y -= platformSpeed;! ! ! }! ! ! if (arrowKeyDown) {! ! ! ! platform1.y += platformSpeed;! ! ! }! ! ! if (platform1.y < 5) platform1.y = 5;! ! ! if (platform1.y > 395) platform1.y = 395;! ! }! }
  15. 15. source files state changes
  16. 16. source files execution
  17. 17. source files execution
  18. 18. mental model input state new state behaviour { x; y } { x; y-speed } { x; y } { x; y+speed } timer { x; y } { x; y } draw platform … … … …
  19. 19. transformation let y = f(x) Imperative Functional x.f() mutation
  20. 20. “one thing I’m discovering is that transforming data is easier to think about than maintaining state.” ! - Dave Thomas
  21. 21. transformations simplify problem decomposition
  22. 22. Move Up Move Down
  23. 23. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  24. 24. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  25. 25. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  26. 26. UP! ! ! { x=0, y=1 }! DOWN! ! { x=0, y=-1 }! LEFT!! ! { x=-1, y=0 }! RIGHT! ! { x=1, y=0 } Keyboard.arrows
  27. 27. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  28. 28. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  29. 29. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  30. 30. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  31. 31. type alias Platform = {x:Int, y:Int}! defaultPlatform = {x=5, y=0}! ! delta = Time.fps 20! input = Signal.sampleOn delta Keyboard.arrows! ! cap x = max 5 <| min x 395! ! p1 : Signal Platform! p1 = foldp ({x, y} s -> {s | y <- cap <| s.y + 5*y}) ! ! defaultPlatform! ! input
  32. 32. Rx Dart Elm Observable Stream Signal = =
  33. 33. see also http://bit.ly/1HPM3ul
  34. 34. Idea See in Action
  35. 35. see also http://bit.ly/1wV46XS
  36. 36. Elm Basics
  37. 37. add x y = x + y
  38. 38. add : Int -> Int -> Int add x y = x + y
  39. 39. calcAngle start end = let distH = end.x - start.x distV = end.y - start.y in atan2 distV distH
  40. 40. calcAngle start end = let distH = end.x - start.x distV = end.y - start.y in atan2 distV distH
  41. 41. calcAngle start end = let distH = end.x - start.x distV = end.y - start.y in atan2 distV distH
  42. 42. multiply x y = x * y triple = multiply 3
  43. 43. multiply x y = x * y triple = multiply 3
  44. 44. double list = List.map (x -> x * 2) list
  45. 45. double list = List.map ((*) 2) list
  46. 46. tuple1 = (2,“three”) tuple2 = (2,“three”, [4, 5])
  47. 47. tuple4 = (,) 2 “three” tuple5 = (,,) 2 “three” [4, 5]
  48. 48. x = { age=42, name=“foo” }
  49. 49. lightweight, labelled data structure
  50. 50. x.age x.name -- 42 -- “foo”
  51. 51. x.age x.name -- 42 -- “foo” .age x .name x -- 42 -- “foo”
  52. 52. -- clone and update y = { x | name <- "bar" }
  53. 53. type alias Character = { age : Int, name : String }
  54. 54. type alias Named a = { a | name : String } type alias Aged a = { a | age : Int }
  55. 55. lady : Named ( Aged { } ) lady = { name=“foo”, age=42 }
  56. 56. getName : Named x -> String getName { name } = name
  57. 57. getName : Named x -> String getName { name } = name ! getName lady -- “foo”
  58. 58. type Status = Flying Pos Speed | Exploding Radius | Exploded
  59. 59. aka. “sums-and-products” data structures
  60. 60. type Status = Flying Pos Speed | Exploding Radius | Exploded sums : choice between variants of a type
  61. 61. products : tuple of types type Status = Flying Pos Speed | Exploding Radius | Exploded
  62. 62. drawCircle x y radius = circle radius |> filled (rgb 150 170 150) |> alpha 0.5 |> move (x, y)
  63. 63. drawCircle x y radius = circle radius |> filled (rgb 150 170 150) |> alpha 0.5 |> move (x, y) filled : Color -> Shape -> Form
  64. 64. drawCircle x y radius = circle radius |> filled (rgb 150 170 150) |> alpha 0.5 |> move (x, y)
  65. 65. drawCircle x y radius = circle radius |> filled (rgb 150 170 150) |> alpha 0.5 |> move (x, y)
  66. 66. “…a clean design is one that supports visual thinking so people can meet their informational needs with a minimum of conscious effort.” ! - Daniel Higginbotham (www.visualmess.com)
  67. 67. Whilst talking with an ex-colleague, a question came up on how to implement the Stable Marriage problem using a message passing approach. Naturally, I wanted to answer that question with Erlang!! ! Let’s first dissect the problem and decide what processes we need and how they need to interact with one another.! ! The stable marriage problem is commonly stated as:! Given n men and n women, where each person has ranked all members of the opposite sex with a unique number between 1 and n in order of preference, marry the men and women together such that there are no two people of opposite sex who would both rather have each other than their current partners. If there are no such people, all the marriages are “stable”. (It is assumed that the participants are binary gendered and that marriages are not same-sex).! From the problem description, we can see that we need:! * a module for man! * a module for woman! * a module for orchestrating the experiment! In terms of interaction between the different modules, I imagined something along the lines of… 2.top-to-bottom 1.left-to-right how we read ENGLISH
  68. 68. public void DoSomething(int x, int y)! {! Foo(y,! Bar(x,! Zoo(Monkey())));! } how we read CODE
  69. 69. public void DoSomething(int x, int y)! {! Foo(y,! Bar(x,! Zoo(Monkey())));! } 2.bottom-to-top 1.right-to-left how we read CODE
  70. 70. Whilst talking with an ex-colleague, a question came up on how to implement the Stable Marriage problem using a message passing approach. Naturally, I wanted to answer that question with Erlang!! ! Let’s first dissect the problem and decide what processes we need and how they need to interact with one another.! ! The stable marriage problem is commonly stated as:! Given n men and n women, where each person has ranked all members of the opposite sex with a unique number between 1 and n in order of preference, marry the men and women together such that there are no two people of opposite sex who would both rather have each other than their current partners. If there are no such people, all the marriages are “stable”. (It is assumed that the participants are binary gendered and that marriages are not same-sex).! From the problem description, we can see that we need:! * a module for man! * a module for woman! * a module for orchestrating the experiment! In terms of interaction between the different modules, I imagined something along the lines of… 2.top-to-bottom 1.left-to-right how we read ENGLISH public void DoSomething(int x, int y)! {! Foo(y,! Bar(x,! Zoo(Monkey())));! } 2.top-to-bottom 1.right-to-left how we read CODE
  71. 71. “…a clean design is one that supports visual thinking so people can meet their informational needs with a minimum of conscious effort.”
  72. 72. drawCircle x y radius = radius |> circle |> filled (rgb 150 170 150) |> alpha 0.5 |> move (x, y) 2.top-to-bottom 1. left-to-right
  73. 73. drawCircle : Int -> Int -> Float -> Form
  74. 74. drawCircle x y = circle >> filled (rgb 150 170 150) >> alpha 0.5 >> move (x, y)
  75. 75. drawCircle x y = circle >> filled (rgb 150 170 150) >> alpha 0.5 >> move (x, y) circle : Float -> Shape
  76. 76. drawCircle x y = (Float -> Shape) >> filled (rgb 150 170 150) >> alpha 0.5 >> move (x, y)
  77. 77. drawCircle x y = (Float -> Shape) >> filled (rgb 150 170 150) >> alpha 0.5 >> move (x, y) Shape -> Form
  78. 78. drawCircle x y = (Float -> Shape) >> (Shape -> Form) >> alpha 0.5 >> move (x, y)
  79. 79. drawCircle x y = (Float -> Shape) >> (Shape -> Form) >> alpha 0.5 >> move (x, y)
  80. 80. drawCircle x y = (Float -> Shape) >> (Shape -> Form) >> alpha 0.5 >> move (x, y)
  81. 81. drawCircle x y = (Float -> Shape) >> (Shape -> Form) >> alpha 0.5 >> move (x, y)
  82. 82. drawCircle x y = (Float -> Form) >> alpha 0.5 >> move (x, y)
  83. 83. drawCircle x y = (Float -> Form) >> (Form -> Form) >> move (x, y)
  84. 84. drawCircle x y = (Float -> Form) >> (Form -> Form) >> move (x, y)
  85. 85. drawCircle x y = (Float -> Form) >> move (x, y)
  86. 86. drawCircle x y = (Float -> Form) >> (Form -> Form)
  87. 87. drawCircle x y = (Float -> Form) >> (Form -> Form)
  88. 88. drawCircle x y = (Float -> Form)
  89. 89. drawCircle : Int -> Int -> (Float -> Form)
  90. 90. greet name = case name of "Yan" -> “hi, theburningmonk" _ -> “hi,“ ++ name
  91. 91. greet name = case name of "Yan" -> “hi, theburningmonk" _ -> “hi,“ ++ name
  92. 92. fizzbuzz n = if | n % 15 == 0 -> "fizz buzz" | n % 3 == 0 -> "fizz" | n % 5 == 0 -> "buzz" | otherwise -> show n
  93. 93. Mouse.position Mouse.clicks Mouse.isDown …
  94. 94. Window.dimension Window.width Window.height
  95. 95. Time.every Time.fps Time.timestamp Time.delay …
  96. 96. Mouse.position : Signal (Int, Int)
  97. 97. Mouse.position : Signal (Int, Int)
  98. 98. Mouse.position : Signal (Int, Int) (10, 23) (3, 16) (8, 10) (12, 5) (18, 3)
  99. 99. Keyboard.lastPressed : Signal Int
  100. 100. Keyboard.lastPressed : Signal Int H E L L O space
  101. 101. Keyboard.lastPressed : Signal Int H E L L O space 72 69 76 76 79 32
  102. 102. map : (a -> b) -> Signal a -> Signal b
  103. 103. <~
  104. 104. Signal of num of pixels in window
  105. 105. ((w, h) -> w*h) <~ Window.dimensions
  106. 106. ((w, h) -> w*h) <~ Window.dimensions Signal (Int, Int)(Int, Int) -> Int
  107. 107. ((w, h) -> w*h) <~ Window.dimensions Signal (Int, Int)(Int, Int) -> Int Signal Int
  108. 108. (10, 10) (15, 10) (18, 12) 100 150 216 ((w, h) -> w*h) <~ Window.dimensions
  109. 109. map2 : (a -> b -> c) -> Signal a -> Signal b -> Signal c
  110. 110. ~
  111. 111. (,) <~ Window.width ~ Window.height Signal Int a -> b -> (a, b) Signal Int
  112. 112. (,) <~ Window.width ~ Window.height Signal Int Int -> Int -> (Int, Int) Signal Int
  113. 113. map3 : (a -> b -> c -> d) -> Signal a -> Signal b -> Signal c -> Signal d
  114. 114. (,,) <~ signalA ~ signalB ~ signalC
  115. 115. map4 : … map5 : … map6 : … map7 : … map8 : …
  116. 116. foldp : (a -> b -> b) -> b -> Signal a -> Signal b
  117. 117. foldp : (a -> b -> b) -> b -> Signal a -> Signal b
  118. 118. foldp (_ n -> n + 1) 0 Mouse.clicks
  119. 119. foldp (_ n -> n + 1) 0 Mouse.clicks
  120. 120. foldp (_ n -> n + 1) 0 Mouse.clicks
  121. 121. 1 3 42 5 foldp (_ n -> n + 1) 0 Mouse.clicks
  122. 122. UP { x=0, y=1 } DOWN { x=0, y=-1 } LEFT { x=-1, y=0 } RIGHT { x=1, y=0 }
  123. 123. merge : Signal a -> Signal a -> Signal a mergeMany : List (Signal a) -> Signal a …
  124. 124. Js Interop, WebGL HTML layout, dependency management, etc.
  125. 125. 8 segments
  126. 126. direction
  127. 127. change change no changenot allowed direction
  128. 128. direction
  129. 129. direction
  130. 130. direction
  131. 131. direction
  132. 132. direction cherry
  133. 133. direction YUMYUMYUM!
  134. 134. direction +1 segment
  135. 135. Demo
  136. 136. github.com/theburningmonk/elm-snake github.com/theburningmonk/elm-missile- command Missile Command Snake
  137. 137. elm-lang.org/try debug.elm-lang.org/try
  138. 138. the

×