Functional Reactive Programming: A Video Game and Piece of CRUD

Functional Reactive Programming: A Video Game and Piece of CRUD

Functional Reactive Programming (FRP) is the best way to think about systems that have to deal with input.

If you disagree, you're wrong and I'll fight you.

This is a crash course to FRP.

But, we won't use a functional language.

• We will make a video game
◦ Space Invaders.
• We won't make a CRUD website
◦ But we'll talk about how we could

1945ab4cdb87eaf5a5c906fa884c29f1?s=128

Scott Robinson

March 17, 2012
Tweet

Transcript

  1. Whilst We Wait 1. Get LÖVE https://love2d.org/ 2. Code along

    git clone https://github.com/quad/invader.love.git
  2. Functional Reactivity A Video Game and A Piece of CRUD

  3. Reactive Programming a = 1 b = 1 c =

    a + b print c 2 b = 2 print c
  4. Functional Reactive Programming

  5. None
  6. Space Invaders git checkout -f WORLD

  7. function love.load() w = meta.apply(world) app = gvt.start(w) end main.lua

  8. Functional World ‏ 0.6s ‐ 0.2s

  9. function love.keypressed(key) if app.state ~= 'ready' then gvt.step(app, 'key.' ..

    key) end end main.lua
  10. Game Loops loop do input update draw wait end

  11. function love.update(dt) if app.state ~= 'ready' then gvt.step(app, 'dt', dt)

    elseif app.state ~= 'stopped' then love.event.push('q') end end main.lua
  12. world.lua return function() await(5, 'key.escape') end

  13. Demo

  14. Event Systems • Main loop • Events • Queue •

    Dispatcher • Handlers
  15. git checkout -f DRAW

  16. main.lua screen = {} function love.load() w = meta.apply(world, {Screen=screen})

    app = gvt.start(w) end
  17. Imperative World ‏ 0.6s ‐ 0.2s Imperative World Screen

  18. world.lua thing = {} function thing:_draw_list() return {{'rectangle', 'line', 10,

    10, 500, 500}} end __.extend(Screen, {thing}) await('key.escape')
  19. main.lua function love.draw() __(screen):chain() :map(function(o) return o:_draw_list() end) :concat() :each(function(d)

    love.graphics[d[1]](unpack(d, 2)) end) end
  20. Demo

  21. Time Check Is this boring?

  22. git checkout -f BULLET

  23. world.lua bullet = Bullet(500, 500, Consts.bullet.v) __.extend(Screen, {bullet})

  24. bullet.lua function draw_list(x, y) return { {'setLineWidth', Consts.bullet.width}, {'line', x,

    y, x, y + Consts.bullet.height}, {'setLineWidth', 1}, } end
  25. bullet.lua return { _draw_list = L(draw_list)(_x, _y), }

  26. “Lift” • draw_list is a pure function • Same input,

    same output • _draw_list is a “lifted” function • Changed input, changed output
  27. bullet.lua function constructor(ix, iy, v) _x = ix _y =

    iy + S(v)
  28. That’s S(v) as in ∫v O H SN AP C

    ALC U LU S
  29. Demo

  30. Chain Reactions Bullet dt S(v) _y _draw_list _x

  31. git checkout -f SWARM

  32. world.lua swarm = Swarm(Consts.swarm.initial.x, Consts.swarm.initial.y) __.extend(Screen, {bullet, swarm})

  33. swarm.lua function constructor(ix, iy) ... _v = Consts.swarm.speed _x =

    ix + S(_v) _y = iy
  34. swarm.lua invaders = __.range(1, Consts.swarm.number) :map(function(n) return Invader(n - 1,

    _x, _y) end)
  35. invader.lua function constructor(n, sx, sy) ... col = n %

    Consts.swarm.columns row = math.floor(n / Consts.swarm.columns) _x = (col * (Consts.invader.side * Consts.invader.spacing.x)) + sx _y = (row * (Consts.invader.side * Consts.invader.spacing.y)) + sy
  36. invader.lua function draw_list(x, y) return {{ 'rectangle', 'line', x, y,

    Consts.invader.side, Consts.invader.side, }} end
  37. swarm.lua function draw_list(invaders, x, y) return __(invaders):chain() :map(function(i) return i:_draw_list()

    end) :concat() :value() end
  38. Demo

  39. Chain Reactions Swarm Invader dt S(v) _x _draw_list _x _y

    _y _draw_list
  40. git checkout -f BOUNCE

  41. None
  42. invader.lua function bounced(x) return x <= 0 or (x +

    Consts.invader.side) >= Consts.screen.width end return { _draw_list=L(draw_list)(_x, _y), _bounced=L(bounced)(_x), }
  43. swarm.lua _bounced = __.reduce( invaders, false, function(c, i) return OR(c,

    i._bounced) end )
  44. swarm.lua function bounce() _v = _v() * -1 _y =

    _y() + Consts.invader.close end link(cond(_bounced), bounce)
  45. Demo

  46. • Imperative exception to Functional graph traversal • Acts like

    a fork • “Reacts” to other Reactors • All reactive variables are Reactors The Reactor
  47. git checkout -f PLAYER

  48. world.lua player = Player(Consts.player.initial.x, Consts.player.initial.y)

  49. player.lua function constructor(ix, iy) ... _x = ix _y =

    iy return { _draw_list = L(draw_list)(_x, _y), }
  50. player.lua function draw_list(x, y) return {{ 'triangle', 'line', x, y,

    x + Consts.player.width, y + Consts.player.height, x - Consts.player.width, y + Consts.player.height, }} end
  51. Demo

  52. git checkout -f MOVE

  53. world.lua link('key.left', player.left) link('key.right', player.right)

  54. player.lua return { _draw_list = L(draw_list)(_x, _y), left = move(-1),

    right = move(1), }
  55. player.lua function move(d) return function() _dir = d end end

  56. player.lua _dir = 0 _x = ix _y = iy

    _v = L(v)(_dir, delay(_x)) _x = ix + S(_v)
  57. Delayed Evaluation _dir v _v _x _int_v

  58. Delayed Evaluation _dir v _v _x _int_v

  59. player.lua function v(dir, x) if x then ... else return

    0 end end
  60. player.lua if x then if (x <= C.player.width and dir

    < 0) or (x >= C.screen.width - C.player.width and dir > 0) then return 0 else return Consts.player.speed * dir end end
  61. Demo

  62. git checkout -f SHOOT

  63. world.lua bullet = Bullet(500, 500, C.bullet.v) function shoot() bullet.shoot( player._x(),

    player._y() - C.bullet.height / 2 ) end link('key. ', shoot) link('key.up', shoot)
  64. bullet.lua function shoot(x, y) _x = x _y = y

    + S(v) end return { _draw_list = L(draw_list)(_x, _y), shoot = shoot, }
  65. Demo

  66. git checkout -f KILL

  67. Collision Detection • In pure implementions, handled “outside” as collision

    events. • In our hybrid, handled as a reactive state.
  68. world.lua swarm = Swarm(C.swarm.initial.x, C.swarm.initial.y, bullet)

  69. swarm.lua local function constructor(ix, iy, bullet) ... return Invader(n -

    1, _x, _y, bullet)
  70. invader.lua _box = L(box)(_x, _y) _hit = L(colliding)(_box, bullet._box) _alive

    = true
  71. invader.lua function box(x, y) return {x=x, y=y, width=Consts.invader.side, height=Consts.invader.side} end

  72. invader.lua function colliding(abox, bbox) ax2, ay2 = abox.x + abox.width,

    abox.y + abox.height bx2, by2 = bbox.x + bbox.width, bbox.y + bbox.height return abox.x < bx2 and ax2 > bbox.x and abox.y < by2 and ay2 > bbox.y end
  73. invader.lua function die() _alive = false end link(cond(_hit), die)

  74. invader.lua function draw_list(alive, x, y) if alive then ... else

    return {} end function bounced(alive, x) if alive then ... else return false end
  75. None
  76. Reactivity Huh! Good God! What is it good for?

  77. None
  78. None
  79. •Model - View - Controller •Browser ⁶ Server •Workflows

  80. Workflow?! complete := seen [ submission review confirmation ]

  81. Event Sourcing Martin Fowler’s

  82. most productivity apps editors not useful? relational databases backup/restore filesystems

    (snapshot) git Redo Undo Event Based Event Based State Based State Based
  83. Bullet Swarm Invader Player key.left player_left player_move key.right player_right player_x

    shoot player_v player_draw_list bullet_shoot player_y bullet_y bullet_x key. key.up bullet_int_v bullet_box bullet_draw_list invader_hit screen swarm_int_v swarm_x swarm_draw_list invader_x invader_bounced swarm_bounced swarm_bounce swarm_v swarm_y invader_y invader_draw_list invader_box invader_die invader_alive player_dir player_int_v dt
  84. •Reactive Programming > ˎ •People demand interactivity •Fork and Fix

    Space Invaders
  85. fin

  86. LuaGravity

  87. Push-pull functional reactive programming http://conal.net/papers/push-pull-frp/

  88. The Fran Tutorial http://conal.net/fran/tutorial.htm