Top Banner
Introduction to FRP with a Haskell Implemented, Quake- like Game Functional Thursday #2 [email protected] Link to this slide: http://bit.ly/sntw-ypai
104

Yampa AFRP Introduction

Apr 10, 2017

Download

ChengHui Weng
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Yampa AFRP Introduction

Introduction to FRP with a Haskell Implemented, Quake-like Game

Functional Thursday #[email protected]

Link to this slide: http://bit.ly/sntw-ypai

Page 2: Yampa AFRP Introduction

Frag

HaskellYampaOpenGLQuake-like FPS Game

Mun Hon Cheong2005License: GPL

GitHub Mirrorbit.ly/sntw-frag

Page 4: Yampa AFRP Introduction

Outline

Yampa & FRP

SignalSignal FunctionSpace-Time LeakArrowArrow SyntaxEventSwitchProgramming withYampa

Yampawww.haskell.org/haskellwiki/Yampa

(not official logo)

Page 5: Yampa AFRP Introduction

Functional Reactive Programming

Signal vs. Event

Page 6: Yampa AFRP Introduction

It's all about time & value

Functional Reactive Programming

H0 1

E L2 3 4 5 6 6.5

L O

Signal a ≈ Time → a

( 0, 'H') ( 1, 'E') ( 2, 'L') ( 6, 'L') ( 6.5, 'O')

Page 7: Yampa AFRP Introduction

Time will be an implicit input in FRP program:

Functional Reactive Programming

x = (1/2) * ((integral (vr + vl)) * cos θ)θ = (1/l) * (integral (vr - vl))

Page 8: Yampa AFRP Introduction

function program() {

};

Time will be an implicit input in FRP program:

Functional Reactive Programming

draw(mouse.x, mouse.y)

Page 9: Yampa AFRP Introduction

function program() {

};

Time will be an implicit input in FRP program:

Functional Reactive Programming

draw(mouse.x, mouse.y)

The abstraction absorbed all explicit "event" handling

Page 10: Yampa AFRP Introduction

Time will be an implicit input in FRP program:

Functional Reactive Programming

xintegral, +, cos...

a ( t0 )a ( t1 )a ( t2 )a ( t3 )

a ( tN )a ( .... )

b ( t0 )b ( t1 )b ( t2 )b ( t3 )b ( .... )

x:: Time → Doublex = (1/2) * ( (integral (vr + vl)) * cos θ)

b ( tN )

Page 11: Yampa AFRP Introduction

Note that the `integral` is stateful (rely on time):

Functional Reactive Programming

x:: Time → Doublex = (1/2) * ( (integral (vr + vl)) * cos θ)

t = N, integral (vr + vl)t from 0 to N...

t = 2, integral (vr + vl)t from 0 to 2t = 1, integral (vr + vl)t from 0 to 1t = 0, integral (vr + vl)t from 0 to 0

Page 12: Yampa AFRP Introduction

In order to perform stateful computations, we must represent our signals in FRP as streams:

Functional Reactive Programming

newtype S a = S ([DTime] → [a])newtype C a = C (a, DTime → C a)

integralS :: Double → S Double → S DoubleintegralC :: Double → C Double → C Double

x = integralS 0 (vr + vl), where vr & vl are Signals x = integralC 0 (vr + vl), where vr & vl are Signals

Page 13: Yampa AFRP Introduction

In fact we can't touch any signal in Yampa. We can only compose Signal Functions

f[ state (t) ]

a ( t ) b ( t )

Signal Function

SF:: Signal a → Signal b

f:: SF a b

Page 14: Yampa AFRP Introduction

f:: SF a b

The `state (t)` summarizes input history:same `f` handle every `a` in different time

SF:: Signal a → Signal b

f[ state (t) ]

a ( t0 )a ( t1 )a ( t2 )a ( t3 )

a ( tN )a ( .... )

b ( t0 )b ( t1 )b ( t2 )b ( t3 )

b ( tN )b ( .... )

Signal Function

Page 15: Yampa AFRP Introduction

"Space-Time Leak"

Why Signal Functions ?

Page 16: Yampa AFRP Introduction

Space-Time Leak in FRP

It means that old records got heaped up and occurs the memory due to our expressions had been expanded improperly.

An analogous example

repeat x = λx → x : repeat x repeat x = λx → let xs = x:xs in xs

Leaks No-Leaks

Page 17: Yampa AFRP Introduction

Space-Time Leak in FRP

repeat x = λx → x : repeat xrepeat 3

↪ (λx → x : repeat x) (3)

↪ 3 : repeat 3

↪ 3 : (λx → x : repeat x) (3)

↪ 3 : 3 : repeat 3

↪ 3 : 3 : (λx → x : repeat x) (3)

↪ 3 : 3 : 3 : repeat 3

↪ ...

It must create new nodes for every `repeat 3`

Page 18: Yampa AFRP Introduction

Space-Time Leak in FRP

repeat x = λx → let xs = x:xs in xsrepeat 3

↪ (λx → let xs = x:xs in xs) (3)

↪ xs, where `xs` was defined as above:

↪ 3: xs, where `xs` was defined as above:

↪ 3: 3: xs, where `xs` was defined as above:

↪ 3: 3: 3: xs, where `xs` was defined as above:

↪ ...

The `let` bind the same `xs` "reference" in every expanded expression, thus there is no need to create new nodes in memory.

Page 19: Yampa AFRP Introduction

Space-Time Leak in FRP

Even though this is just an analogous example, but a similar idea is in Arrow:

class Arrow a ⇒ ArrowLoop a where loop :: a (b,d) (c,d) → a b c

instance ArrowLoop (→ ) where

loop f b = let (c,d) = f (b,d) in c

And Yampa uses Arrow to prevent space- time leaks, especially the ArrowLoop.

Page 20: Yampa AFRP Introduction

Space-Time Leak in FRP

RECURSIVE CODES MAY DESTRUCT YOUR MIND

Page 21: Yampa AFRP Introduction

Space-Time Leak in FRP

A practical example:

This example will show that space-time leak may happen when a computation is stateful ( based on time )

The Exponential Function

Page 22: Yampa AFRP Introduction

Space-Time Leak in FRP

Because in practical systems we only care about how to compute on continuous streams of values, we can represent our Signals as streams in two forms:

newtype S a = S ([DTime] → [a])newtype C a = C (a, DTime → C a)

-- delta time, for samplingtype DTime = Double

The later one will expand its second (C a) to make a continuing stream.

Page 23: Yampa AFRP Introduction

Space-Time Leak in FRP

And the integral functions:

integralS :: Double → S Double → S DoubleintegralS i (S f) = S (λdts → scanl (+) i (zipWith (∗) dts (f dts)))

integralC :: Double → C Double → C DoubleintegralC i (C p) = C (i, λdt→integralC (i + fst p ∗ dt) (snd p dt))

We assume that delta time is fixed, which is impractical in real system, where it will depends on processor speed, computational load, interrupts, and so on

Page 24: Yampa AFRP Introduction

Space-Time Leak in FRP

Remember that the integral need to accumulate each values at every DTime:

DTime

Page 25: Yampa AFRP Introduction

That's why it need to expand the stream while evaluation:

e = integralC 1 e = integralC i:1 (C p):e=integralC 1 e=...↪ C (1, λdt→integralC (1 + fst p ∗ dt) (snd p dt) )

↪ C (1, λdt→integralC (1 + 1 ∗ dt) (snd p dt) )

Space-Time Leak in FRP

p

fst P

integralC :: Double → C Double → C DoubleintegralC i (C p) = C (i, λdt→integralC (i + fst p ∗ dt) (snd p dt))

Page 26: Yampa AFRP Introduction

That's why it need to expand the stream while evaluation:

e = integralC 1 e = integralC i:1 (C p):e=integralC 1 e=...↪ C (1, λdt→integralC (1 + fst p ∗ dt) (snd p dt) )

↪ C (1, λdt→integralC (1 + 1 ∗ dt) (snd p dt) )

Space-Time Leak in FRP

psnd p

Page 27: Yampa AFRP Introduction

That's why it need to expand the stream while evaluation:

e = integralC 1 e = integralC i:1 (C p):e=integralC 1 e=...↪ C (1, λdt→integralC (1 + fst p ∗ dt) (snd p dt) )

↪ C (1, λdt→integralC (1 + 1 ∗ dt) (sndp dt) )

Space-Time Leak in FRP

psnd p

Page 28: Yampa AFRP Introduction

That's why it need to expand the stream while evaluation:

e = integralC 1 e = integralC i:1 (C p):e=integralC 1 e=...↪ C (1, λdt→integralC (1 + fst p ∗ dt) (snd p dt) )

↪ C (1, λdt→integralC (1 + 1 ∗ dt) (sndp dt) )

↪ C (1, q )

Space-Time Leak in FRP

psnd p

Page 29: Yampa AFRP Introduction

Now we may want to "run" the stream to see what will happen:

run :: C Double → [Double]run (C p) = fst p : run (snd p dt)

Space-Time Leak in FRP

Page 30: Yampa AFRP Introduction

Now we may want to "run" the stream to see what will happen:

run e, where run (C p) = fst p : run (snd p dt)

↪ run C (1, q ), because e = C (1, q ); then evaluate the run:

↪ 1 : run (q dt)

↪ 1 : run ((λdt→integralC (1 + 1 ∗ dt) (q dt) ) dt)

↪ 1 : run (integralC (1 + 1 ∗ dt) (q dt) )

↪ let's expand the horrible q now...

Space-Time Leak in FRP

Page 31: Yampa AFRP Introduction

run e, where run (C p) = fst p : run (snd p dt)

↪ run C (1, q ), because e = C (1, q ); then evaluate the run:

↪ 1 : run (q dt)

↪ 1 : run ((λdt→integralC (1 + 1 ∗ dt) (qdt) ) dt)

↪ 1 : run (integralC (1 + 1 ∗ dt) (q dt) )

↪ 1 : run (C ( (1 + 1 ∗ dt),

(λdt→integralC (1 + fst p ∗ dt) (snd p dt) ) (dt) )

Space-Time Leak in FRP

Page 32: Yampa AFRP Introduction

run e, where run (C p) = fst p : run (snd p dt)

↪ run C (1, q ), because e = C (1, q ); then evaluate the run:

↪ 1 : run (q dt)

↪ 1 : run ((λdt→integralC (1 + 1 ∗ dt) (qdt) ) dt

↪ 1 : run (integralC (1 + 1 ∗ dt) (q dt) )

↪ 1 : run (C ( (1 + 1 ∗ dt),

(λdt→integralC (1 + fst p ∗ dt) (snd p dt) ) (dt) )

↪ 1 : run (C ( (1 + 1 ∗ dt),

(λdt→integralC (1 + fst p ∗ dt) (snd p dt) ) (dt) )

Space-Time Leak in FRP

new P new Q

Page 33: Yampa AFRP Introduction

↪ 1 : run (C ( (1 + 1 ∗ dt),

(λdt→integralC (1 + fst p ∗ dt) (snd p dt) ) (dt) )

↪ 1 : run (C ( (1 + 1 ∗ dt), q ))↪ 1 : run ((1 + 1 ∗ dt): run (q dt)) ↪ ...

Space-Time Leak in FRP

This leads to O(n) space ,and O(n2) time similar to the simplifier `repeat` example

We hope to reduce it to O(Const) space, and O(n) time to compute it

Page 34: Yampa AFRP Introduction

Space-Time Leak in FRP

The main issue here is the progress of evaluation can't recognize this:

Is the same with:

Just like the `repeat` example

f = λdt → integralC (1 + dt) (f dt)

f = λdt → let x = integralC (1 + dt) x in x

Page 35: Yampa AFRP Introduction

Space-Time Leak in FRP

And we can compare them by diagrams:

f = λdt → integralC (1 + dt) (f dt)

f = λdt → let x = integralC (1 + dt) x in x

Page 36: Yampa AFRP Introduction

Space-Time Leak in FRP

In the `repeat` example:

repeat x = λx → x : repeat x

repeat x = λx → let xs = x:xs in xs

Page 37: Yampa AFRP Introduction

Space-Time Leak in FRP

So, in Yampa, we can't touch and handle signals directly, and need to use predefined combinators to compose our program.

This can greatly reduce possible leaks, and keep our program is still similar with the most intuitive version.

e = integralC 1 e e = proc () → do rec e ← integral 1 ↢e returnA ↢ e

Page 38: Yampa AFRP Introduction

Space-Time Leak in FRP

integralSF :: Double → SF Double DoubleintegralSF i = SF (λx → (i, λdt → integralSF (i + dt ∗ x)))

integralC :: Double → C Double → C DoubleintegralC i (C p) = C (i, λdt→integralC (i + fst p ∗ dt) (snd p dt))

Versus the previous version:

Our integral function now become:

Signal handling become implicit. We now raise customized functions to SFs.

Page 39: Yampa AFRP Introduction

Space-Time Leak in FRP

integralSF :: Double → SF Double DoubleintegralSF i = SF (λx → (i, λdt → integralSF (i + dt ∗ x)))

Now integralSF need be embedded in Arrow structure to feed input in & run it:

e = proc () → do rec e ← integral 1 ↢e returnA ↢ e

runSF:: SF () Double → [Double]runSF e

Page 40: Yampa AFRP Introduction

Back to Yampa

Yampa & AFRP

SignalSignal FunctionSpace-Time LeakArrowArrow SyntaxEventSwitchProgramming withYampa

Yeah ! Just escaped from the black hole !

Page 41: Yampa AFRP Introduction

Yampa constructs the whole system by combining Signal Functions

Combine Signal Functions

gfa b c

h:: SF a c

Page 42: Yampa AFRP Introduction

Yampa constructs the whole system by combining Signal Functions

Combine Signal Functions

gfa b c

... and there's already a natural mechanism to achieve this in Haskell

Page 43: Yampa AFRP Introduction

Yampa constructs the whole system by combining Signal Functions

Combine Signal Functions

gfa b c

( ⋙):: SF a b → SF b c → SF a c

"Composition" in Control.Arrow

Page 44: Yampa AFRP Introduction

So Yampa is actually an AFRP framework, not only a FRP framework.

Combine Signal Functions

gfa b c

( ⋙):: SF a b → SF b c → SF a c

"Composition" in Control.Arrow

Page 46: Yampa AFRP Introduction

Arrow Basics

"Unlike Monad and Applicative, whose types only reflect their output, the type of an Arrow computation reflects both its input and output"

( ≫= ):: M a → (a → M b) → M b

( ⋙ ):: A a b → A b c → A a c

Page 47: Yampa AFRP Introduction

Arrow Basics

In Monad the binding generates a contexted value like `IO String` from an opaque function , and it can output such values without feeding any input

( ≫= ):: M a → (a → M b) → M b

Page 48: Yampa AFRP Introduction

Arrow Basics

In Monad the binding generates a contexted value like `IO String` from an opaque function , and it can output such values without feeding any input

( ≫= ):: M a → (a → M b) → M b

When you got a composed `IO String`, you don't need to feed it any input to get the output. The "input" is already encapsulated in the Monad.

Ex: readFile "/tmp/foo.txt" :: IO String

Page 49: Yampa AFRP Introduction

Arrow Basics

Arrows, on the other hand, can only be composed by other arrow combinators, and keep it still crystal clear after the composition

( ⋙ ):: A a b → A b c → A a c

Page 50: Yampa AFRP Introduction

Arrow Basics

Arrows, on the other hand, can only be composed by other arrow combinators, and keep it still crystal clear after the composition

( ⋙ ):: A a b → A b c → A a cYou can't get any meaningful value without executing the composed Arrow, which will also require you to feed it an input value.

Ex: let area = runF ( pow ⋙ mulpi ) r

Page 51: Yampa AFRP Introduction

Arrow Basics

So Arrows are just like pipelines, and Monads are more like pipeline plus self-contained pumps

Page 52: Yampa AFRP Introduction

There are many combinators in Control.Arrow

Arrow Basics

Compose First

f g f

Split

f

g

f

Loop ( self-feedback )

Page 53: Yampa AFRP Introduction

They will make your application like circuits

Arrow Basics

Page 54: Yampa AFRP Introduction

Back to Yampa

Yampa & AFRP

SignalSignal FunctionSpace-Time LeakArrowArrow SyntaxEventSwitchProgramming withYampa

Yampawww.haskell.org/haskellwiki/Yampa

(not official logo)

Page 55: Yampa AFRP Introduction

Using Arrows without some syntax sugars will make programs a bit very ugly

Arrow Syntax

proc x → do y ← f ↢ x+1 g ← 2*y let z = x+y t ← h ↢ x*z returnA ↢ t+z

arr (λ x → (x, x)) ⋙ first (arr (\ x → x+1) ⋙ f) ⋙ arr (\ (y, x) → (y, (x, y))) ⋙ first (arr (\ y → 2*y) ⋙ g) ⋙ arr snd ⋙ arr (\ (x, y) → let z = x+y in ((x, z), z)) ⋙ first (arr (\ (x, z) → x*z) ⋙ h) ⋙ arr (\ (t, z) → t+z) ⋙ returnA

http://www.haskell.org/ghc/docs/6.12.3/html/users_guide/arrow-notation.html

Page 56: Yampa AFRP Introduction

Using Arrows without some syntax sugars will make programs a bit very ugly

Arrow Syntax

proc x → do y ← f ↢ x+1 g ← 2*y let z = x+y t ← h ↢ x*z returnA ↢ t+z

proc <pattern> = λ→ <pattern>

<pattern> ← <arrf> ↢<expr> ≈let <pattern> = <arrf> <expr>

rec <do block> = ArrowLoop

http://stackoverflow.com/questions/5405850/how-does-the-haskell-rec-keyword-work

Page 57: Yampa AFRP Introduction

In Yampa, Event is just a Maybe-like type, representing discrete signals

Event

data Event a = NoEvent | Event atag:: Event a → b → Event b

We usually use events to trigger switchers, which will change the structure of our system dynamically.

Page 58: Yampa AFRP Introduction

Arrows will make your application like circuits. But you own a dynamic system now, not a static one.

Switches

Time 0Time 1Time 2Time 3Time 4Time 5Time 6Time 7Time ...Time N

Page 59: Yampa AFRP Introduction

This means circuits won't change unless we introduce Switches

Switches

Normal signals will be SFs' input; only events will go through the line toward the `k` function with data `mng`. If so the continuation function `k` will spawn a new SF or kill old SFs in the system.

Page 60: Yampa AFRP Introduction

A pseudo example shows how switches works

Switches

f Kin out System @ T01 SF inside a Switch. Switch will let normal signal pass

f Kin out System @ T1`f` generate an Event Spawn,rather than a Signal with data

Spawn

f Kin out System @ T1`K` captured the event, and generate a new SF `g`

g

Page 61: Yampa AFRP Introduction

A pseudo example shows how switches works

Switches

f Kin out System @ T1Then `K` put the new SF in our system.g

System @ T2Now we've 2 SFs and inputs will be dispatched to them all

f Kin out

g

Page 62: Yampa AFRP Introduction

A pseudo example shows how switches works

Switches

f Kin out System @ T3`g` trigger a Kill event with id 'f'

g Kill

Kin out System @ T3`K` will kill the SF `f`

gf

Kin out System @ T4Done. This shows our system will change by time.

g

Note: we omitted many details to keep this example clean enough. For instance, we can't kill named SFs after all. They're actually IL Objects.

Page 63: Yampa AFRP Introduction

Switches, more switches... ( cry )

Switches

Page 64: Yampa AFRP Introduction

Switches, more switches... ( cry )

Switches

Page 66: Yampa AFRP Introduction

How is it possible to use these stuff making a 3D game ?

Switches

Page 67: Yampa AFRP Introduction

Programming withYampa

Game ObjectsRouteKill or SpawndpSwitch

Yampa Acradehttp://bit.ly/sntw-ypa

Page 68: Yampa AFRP Introduction

Game objects, like dragons, weapons, players and NPCs are just SFs which receive inputs like the position and velocity, and output the new, updated GameStates

Game Objects

fin out

g

Page 69: Yampa AFRP Introduction

Game objects, like dragons, weapons, players and NPCs are just SFs which receive inputs like the position and velocity, and output the new, updated GameStates

Game Objects

type Object = SF ObjInput ObjOutputFrom the file "Object.hs" in the game Frag.

Page 70: Yampa AFRP Introduction

Object inputs in Frag:

Game Objects

data ObjInput = ObjInput { oiHit :: !(Event [(ILKey,ObsObjState)]), oiMessage :: !(Event [(ILKey,Message)]), oiCollision :: !Camera, oiCollisionPos :: !(Double,Double,Double), oiOnLand :: !Bool, oiGameInput :: !GameInput, oiVisibleObjs :: !(Event [(ILKey,ObsObjState)])}

Page 71: Yampa AFRP Introduction

Object outputs in Frag:

Game Objects

data ObjOutput = ObjOutput { ooObsObjState :: !ObsObjState, ooSendMessage :: !(Event [(ILKey,(ILKey,Message))]),

ooKillReq :: (Event ()), ooSpawnReq :: (Event [ILKey->Object])}

Page 72: Yampa AFRP Introduction

A simple game object from Yampa Arcade

Game Objects

data SimpleGunState = SimpleGunState { sgsPos :: Position2, sgsVel :: Velocity2, sgsFired :: Event ()}

type SimpleGun = SF GameInput SimpleGunState

Source code:http://hackage.haskell.org/package/SpaceInvaders

Page 73: Yampa AFRP Introduction

A simple game object from Yampa Arcade

Game Objects

gun

GameInput

Mouse PositionMouse ButtonKeyboard Input

Calculate new position and velocity of the gun

SimpleGunState

sgsPossgsVelsgsFired Event

Page 74: Yampa AFRP Introduction

In fact we don't define a SF. We define a SF generator :

Game Objects

simpleGun :: Position2 → SimpleGun

simpleGun (Point2 x0 y0) = proc gi → do (Point2 xd _) ← ptrPos ↢ gi rec let ad = 10 * (xd - x) - 5 * v v ← integral ↢ clampAcc v ad {- ...... -} returnA -< SimpleGunState { sgsPos = (Point2 x y0), sgsVel = (vector2 v 0), sgsFired = fire }

Page 75: Yampa AFRP Introduction

This allow us to embed it into our circuits and change the initial value as we need to:

Game Objects

game g nAliens vydAlien score0 = proc gi -> do rec oos <- game' objs0 -< (gi, oos {- oosp -}) {- ...... -} returnA -< ((score, map ooObsObjState (elemsIL oos)), (newRound `tag` (Left score)) `lMerge` (gameOver `tag` (Right score))) where objs0 = listToIL (gun (Point2 0 50))

Page 76: Yampa AFRP Introduction

Now we have game objects. Then we've to use a `route` function to pair each object with an input.

Route

route:: BSPMap → (GameInput, IL ObjOutput) → IL sf → IL (ObjInput, sf)

From the file "Game.hs" in the game Frag.

Page 77: Yampa AFRP Introduction

Now we have game objects. Then we've to use a `route` function to pair each object with an input.

Route

route:: BSPMap → (GameInput, IL ObjOutput) → IL sf → IL (ObjInput, sf)

Route will broadcast GameInput to all game object which are like keyboard & mouse state , and handle outputs from game objects to send messages or detect collisions, then only pair it with those related objects.

Page 78: Yampa AFRP Introduction

Now we have game objects. Then we've to use a `route` function to pair each object with an input.

Route

gun

NPC#1

PC

Wall

RGameInput

ObjOutput

Decide which objects should be dispatched with current ObjOutputs

Page 79: Yampa AFRP Introduction

Now we have game objects. Then we've to use a `route` function to pair each object with an input.

Route

route:: BSPMap → (GameInput, IL ObjOutput) → IL sf → IL (ObjInput, sf)

`IL a` means a "identity list", which is actually a associate list contains `(ILKey , a)`. Frag use it to store named game objects ( just SFs ) .

Page 80: Yampa AFRP Introduction

Now we have game objects. Then we've to use a `route` function to pair each object with an input.

Route

route:: BSPMap → (GameInput, IL ObjOutput) → IL sf → IL (ObjInput, sf)

Finally the route generate a associate list, pairing each SF with an object input. Note that the original ObjOutput may be converted inside the function.

Page 81: Yampa AFRP Introduction

The function `KillOrSpawn` will capture all events generated by game objects, and find if any kill or spawn events occured.

Kill or Spawn

killOrSpawn :: (a, IL ObjOutput)→ (Event (IL Object→IL Object))

From the file "Game.hs" in the game Frag.

Page 82: Yampa AFRP Introduction

The function `KillOrSpawn` will capture all events generated by game objects, and find if any kill or spawn events occured.

Kill or Spawn

killOrSpawn :: (a, IL ObjOutput)→ (Event (IL Object→IL Object))It'll receive lots of object outputs, then generate:

1. NoEvent: do nothing ( rem: Event can be NoEvent )2. Kill: with a function that will kill some SFs3. Spawn: with a function that will spawn new SFs

The function will apply on object collections then change it

Page 83: Yampa AFRP Introduction

The function `KillOrSpawn` will capture all events generated by game objects, and find if any kill or spawn events occured.

Kill or Spawn

f Kin out

g

ooKillReqWill generate an Event to kill some SFs in the collection

f Kin out

g

ooSpawnReq

Will generate an Event to spawn new SFs in the collection

Page 84: Yampa AFRP Introduction

Our `route` and `killOrSpwan` are prepared for the `dpSwitch`, which maintain whole dynamic structure in our program

dpSwitch

Page 85: Yampa AFRP Introduction

Our `route` and `killOrSpwan`are prepared for the `dpSwitch`, which maintain whole dynamic structure in our program

dpSwitch

dpSwitch :: Functor col ⇒(∀ sf . (a → col sf → col (b, sf)))→ col (SF b c)→ SF (a, col c) (Event d)→ (col (SF b c) -> d -> SF a (col c))→ SF a (col c)

Page 86: Yampa AFRP Introduction

dpSwitch :: Functor col ⇒(∀ sf . (a → col sf → col (b, sf)))→ col (SF b c)→ SF (a, col c) (Event d)→ (col (SF b c) -> d -> SF a (col c))→ SF a (col c)

The first argument is our routing function.

dpSwitch

Page 87: Yampa AFRP Introduction

dpSwitch :: Functor col ⇒(∀ sf . (a → col sf → col (b, sf)))→ col (SF b c)→ SF (a, col c) (Event d)→ (col (SF b c) -> d -> SF a (col c))→ SF a (col c)

The second one is the initial collection of game objects.

dpSwitch

Page 88: Yampa AFRP Introduction

dpSwitch :: Functor col ⇒(∀ sf . (a → col sf → col (b, sf)))→ col (SF b c)→ SF (a, col c) (Event d)→ (col (SF b c) -> d -> SF a (col c))→ SF a (col c)

And the third argument is our `killOrSpawn`.

dpSwitch

Page 89: Yampa AFRP Introduction

dpSwitch :: Functor col ⇒(∀ sf . (a → col sf → col (b, sf)))→ col (SF b c)→ SF (a, col c) (Event d)→ (col (SF b c) -> d -> SF a (col c))→ SF a (col c)

The fourth function will be invoked while switching event occurs, yielding a new switch function and switch into, based on the collection previous transformed.

dpSwitch

Page 90: Yampa AFRP Introduction

dpSwitch :: Functor col ⇒(∀ sf . (a → col sf → col (b, sf)))→ col (SF b c)→ SF (a, col c) (Event d)→ (col (SF b c) -> d -> SF a (col c))→ SF a (col c)

This allows the collection to be updated and then switched back in, typically by employing `dpSwitch` again.

dpSwitch

Page 91: Yampa AFRP Introduction

Now we have a dynamic structure can compose every piece in our game, so the Quake warrior can roar with firing guns, right ?

Some Other Stuff

Page 92: Yampa AFRP Introduction

Still Missing

NPC & AILevelsOpenGL Binding...

There is no royal road to Haskell. —Euclid ( typeclassopedia )

Page 93: Yampa AFRP Introduction

Maybe we're still not apt to create a 3D game with the AFRP framework, Yampa, but ideas in FRP & Arrow still inspire us.

Conclusion

Page 95: Yampa AFRP Introduction

Some differences between FRP and so-called "Event-Driven" pattern:

Conclusion

FRP based on continuing Streams of values, and we compute on them as we compute on a single value.

dt1 dt2 dt3 dt4 dt5

sf

sf can be primitive SF, composed SF or Switch

Page 96: Yampa AFRP Introduction

Some differences between FRP and so-called "Event-Driven" pattern:

Conclusion

Event-Driven use individual callbacks to react at every moment the event got triggered.

T1 T2 T3 T4 T5

cb

cb is a simple function, closure,or class method

cb cb cb cb

Page 97: Yampa AFRP Introduction

Conclusion

This can be obvious in JavaScript and Node.js, which use Event-Driven as their reactive pattern

DOM.addEventListener('click', function(e){ // do something})

fs.readFile( '/tmp/test.txt', function(err, data){ // do something})

Page 98: Yampa AFRP Introduction

Conclusion

But some libraries also try to implement FRP paradigm in JavaScript:

// from Bacon.js

var plus = $("#plus").asEventStream("click").map(1)var minus = $("#minus").asEventStream("click").map(-1)var both = plus.merge(minus)

Page 99: Yampa AFRP Introduction

Conclusion

But some libraries also try to implement FRP paradigm in JavaScript:

// from Flapjax.js, compiler mode

<div><h1> You caught up <span style="color: white; background-color: black"> {! caughtUpB.toString() !} </span>times</h1>hit up with your mouse</div>

Page 100: Yampa AFRP Introduction

Conclusion

But some libraries also try to implement FRP paradigm in JavaScript:

http://elm-lang.org/learn/What-is-FRP.elm

Page 101: Yampa AFRP Introduction

Conclusion

And you may notice that in FRP, events/signals are handled "globally", but in JavaScript, they're handled "locally":

DOM → click, mouse hover...

click, mouse hover → Event DOM// FRP

// Event-Driven

Page 102: Yampa AFRP Introduction

References

There's a lot of resources in Haskell Wikihttp://www.haskell.org/haskellwiki/Yampa