KEMBAR78
Yampa AFRP Introduction | PDF
Introduction to FRP with a
Haskell Implemented, Quake-
like Game
Functional Thursday #2
snowmantw@gmail.com
Link to this slide: http://bit.ly/sntw-ypai
Frag
Haskell
Yampa
OpenGL
Quake-like
FPS Game
Mun Hon Cheong
2005
License: GPL
GitHub Mirror
bit.ly/sntw-frag
Snowmantw
a Programming
Language
Enthusiast
Haskell
JavaScript
Rust C/C++ Python
Ruby
Implemented
GitHub Profile
github.com/
snowmantw
Outline
Yampa & FRP
Signal
Signal Function
Space-Time Leak
Arrow
Arrow Syntax
Event
Switch
Programming with
Yampa
Yampa
www.haskell.org/
haskellwiki/Yampa
(not official logo)
Functional Reactive Programming
Signal vs. Event
It's all about time & value
Functional Reactive Programming
H
0 1
E L
2 3 4 5 6 6.5
LO
Signal a ≈ Time → a
( 0, 'H') ( 1, 'E') ( 2, 'L') ( 6, 'L') ( 6.5, 'O')
Time will be an implicit input in FRP program:
Functional Reactive Programming
x = (1/2) * ((integral (vr + vl)) * cos θ)
θ = (1/l) * (integral (vr - vl))
function program() {
};
Time will be an implicit input in FRP program:
Functional Reactive Programming
draw(mouse.x,
mouse.y)
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
Time will be an implicit input in FRP program:
Functional Reactive Programming
x
integral, +, cos...
a ( t0 )
a ( t1 )
a ( t2 )
a ( t3 )
a ( tN )
a ( .... )
b ( t0 )
b ( t1 )
b ( t2 )
b ( t3 )
b ( .... )
x:: Time → Double
x = (1/2) * ( (integral (vr + vl)) * cos θ)
b ( tN )
Note that the `integral` is stateful (rely on time):
Functional Reactive Programming
x:: Time → Double
x = (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 2
t = 1, integral (vr + vl)t
from 0 to 1
t = 0, integral (vr + vl)t
from 0 to 0
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 Double
integralC :: 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
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
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
"Space-Time Leak"
Why Signal Functions ?
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
Space-Time Leak in FRP
repeat x = λx → x : repeat x
repeat 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`
Space-Time Leak in FRP
repeat x = λx → let xs = x:xs in xs
repeat 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.
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.
Space-Time Leak in FRP
RECURSIVE CODES
MAY DESTRUCT
YOUR MIND
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
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 sampling
type DTime = Double
The later one will expand its second (C a)
to make a continuing stream.
Space-Time Leak in FRP
And the integral functions:
integralS :: Double → S Double → S Double
integralS i (S f) = S (λdts → scanl (+) i
(zipWith (∗) dts (f dts)))
integralC :: Double → C Double → C Double
integralC 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
Space-Time Leak in FRP
Remember that the integral need to accumulate
each values at every DTime:
DTime
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 Double
integralC i (C p) = C (i, λdt→integralC (i + fst p ∗ dt) (snd p dt))
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
snd p
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
p
snd p
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
p
snd p
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
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
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
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
↪ 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
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
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
Space-Time Leak in FRP
In the `repeat` example:
repeat x = λx → x : repeat x
repeat x = λx → let xs = x:xs in xs
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
Space-Time Leak in FRP
integralSF :: Double → SF Double Double
integralSF i = SF (λx → (i, λdt → integralSF (i + dt ∗ x)))
integralC :: Double → C Double → C Double
integralC 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.
Space-Time Leak in FRP
integralSF :: Double → SF Double Double
integralSF 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
Back to Yampa
Yampa & AFRP
Signal
Signal Function
Space-Time Leak
Arrow
Arrow Syntax
Event
Switch
Programming with
Yampa
Yeah ! Just escaped from
the black hole !
Yampa constructs the whole system by combining
Signal Functions
Combine Signal Functions
gf
a b c
h:: SF a c
Yampa constructs the whole system by combining
Signal Functions
Combine Signal Functions
gf
a b c
... and there's already a natural
mechanism to achieve this in Haskell
Yampa constructs the whole system by combining
Signal Functions
Combine Signal Functions
gf
a b c
( ⋙):: SF a b → SF b c → SF a c
"Composition" in Control.Arrow
So Yampa is actually an AFRP framework, not
only a FRP framework.
Combine Signal Functions
gf
a b c
( ⋙):: SF a b → SF b c → SF a c
"Composition" in Control.Arrow
Arrow
We're here:
Typeclassopedia
www.haskell.org/
haskellwiki/
Typeclassopedia
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
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
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
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
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
You 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
Arrow Basics
So Arrows are just like pipelines, and Monads are
more like pipeline plus self-contained pumps
There are many combinators in Control.Arrow
Arrow Basics
Compose First
f g f
Split
f
g
f
Loop ( self-feedback )
They will make your application like circuits
Arrow Basics
Back to Yampa
Yampa & AFRP
Signal
Signal Function
Space-Time Leak
Arrow
Arrow Syntax
Event
Switch
Programming with
Yampa
Yampa
www.haskell.org/
haskellwiki/Yampa
(not official logo)
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
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
In Yampa, Event is just a Maybe-like type,
representing discrete signals
Event
data Event a = NoEvent | Event a
tag:: Event a → b → Event b
We usually use events to trigger switchers, which will
change the structure of our system dynamically.
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
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.
A pseudo example shows how switches works
Switches
f Kin out
System @ T0
1 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
A pseudo example shows how switches works
Switches
f Kin out
System @ T1
Then `K` put the new SF in
our system.
g
System @ T2
Now we've 2 SFs and inputs
will be dispatched to them all
f Kin out
g
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`
g
f
Kin out
System @ T4
Done. 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.
Switches, more switches... ( cry )
Switches
Switches, more switches... ( cry )
Switches
Switches, more switches... ( cry )
Switches
http://www.haskell.org/haskellwiki/
Yampa/switch
How is it possible to use these stuff making a 3D
game ?
Switches
Programming with
Yampa
Game Objects
Route
Kill or Spawn
dpSwitch
Yampa Acrade
http://bit.ly/sntw-ypa
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
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 ObjOutput
From the file "Object.hs" in the game Frag.
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)])
}
Object outputs in Frag:
Game Objects
data ObjOutput = ObjOutput
{
ooObsObjState :: !ObsObjState,
ooSendMessage :: !(Event [(ILKey,(ILKey,Message))]),
ooKillReq :: (Event ()),
ooSpawnReq :: (Event [ILKey->Object])
}
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
A simple game object from Yampa Arcade
Game Objects
gun
GameInput
Mouse Position
Mouse Button
Keyboard Input
Calculate new position
and velocity of the gun
SimpleGunState
sgsPos
sgsVel
sgsFired Event
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 }
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))
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.
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.
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
R
GameInput
ObjOutput
Decide which objects should be
dispatched with current ObjOutputs
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 ) .
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.
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.
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 SFs
3. Spawn: with a function that will spawn new SFs
The function will apply on object collections then change it
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
ooKillReq
Will 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
Our `route` and `killOrSpwan` are prepared for the
`dpSwitch`, which maintain whole dynamic
structure in our program
dpSwitch
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)
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
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
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
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
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
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
Still Missing
NPC & AI
Levels
OpenGL Binding
...
There is no royal road to Haskell.
—Euclid ( typeclassopedia )
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
And FRP is also a generic way to construct any "Event-
Driven" like system, from 3D FPS games to HTTP
servers.
Conclusion
http://stackoverflow.com/questions/13486293/
is-frp-a-proper-way-to-implement-most-event-driven-things
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
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
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
})
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)
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>
Conclusion
But some libraries also try to implement FRP paradigm
in JavaScript:
http://elm-lang.
org/learn/What-is-FRP.
elm
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
References
There's a lot of resources in Haskell Wiki
http://www.haskell.org/haskellwiki/Yampa
References
Two useful articles about Yampa and game
http://www.cse.unsw.edu.au/~pls/thesis/munc-thesis.pdf
http://haskell.cs.yale.edu/wp-content/uploads/2011/01/yampa-arcade.
pdf
Yampa and robot control
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.5.2886
&rep=rep1&type=pdf
References
Space-Time Leak:
http://conal.net/blog/posts/trimming-inputs-in-functional-reactive-
programming
http://cs-www.cs.yale.edu/homes/hl293/download/leak.pdf
http://www.cs.ox.ac.uk/ralf.hinze/WG2.8/24/slides/paul.pdf

Yampa AFRP Introduction

  • 1.
    Introduction to FRPwith a Haskell Implemented, Quake- like Game Functional Thursday #2 snowmantw@gmail.com Link to this slide: http://bit.ly/sntw-ypai
  • 2.
    Frag Haskell Yampa OpenGL Quake-like FPS Game Mun HonCheong 2005 License: GPL GitHub Mirror bit.ly/sntw-frag
  • 3.
    Snowmantw a Programming Language Enthusiast Haskell JavaScript Rust C/C++Python Ruby Implemented GitHub Profile github.com/ snowmantw
  • 4.
    Outline Yampa & FRP Signal SignalFunction Space-Time Leak Arrow Arrow Syntax Event Switch Programming with Yampa Yampa www.haskell.org/ haskellwiki/Yampa (not official logo)
  • 5.
  • 6.
    It's all abouttime & value Functional Reactive Programming H 0 1 E L 2 3 4 5 6 6.5 LO Signal a ≈ Time → a ( 0, 'H') ( 1, 'E') ( 2, 'L') ( 6, 'L') ( 6.5, 'O')
  • 7.
    Time will bean implicit input in FRP program: Functional Reactive Programming x = (1/2) * ((integral (vr + vl)) * cos θ) θ = (1/l) * (integral (vr - vl))
  • 8.
    function program() { }; Timewill be an implicit input in FRP program: Functional Reactive Programming draw(mouse.x, mouse.y)
  • 9.
    function program() { }; Timewill be an implicit input in FRP program: Functional Reactive Programming draw(mouse.x, mouse.y) The abstraction absorbed all explicit "event" handling
  • 10.
    Time will bean implicit input in FRP program: Functional Reactive Programming x integral, +, cos... a ( t0 ) a ( t1 ) a ( t2 ) a ( t3 ) a ( tN ) a ( .... ) b ( t0 ) b ( t1 ) b ( t2 ) b ( t3 ) b ( .... ) x:: Time → Double x = (1/2) * ( (integral (vr + vl)) * cos θ) b ( tN )
  • 11.
    Note that the`integral` is stateful (rely on time): Functional Reactive Programming x:: Time → Double x = (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 2 t = 1, integral (vr + vl)t from 0 to 1 t = 0, integral (vr + vl)t from 0 to 0
  • 12.
    In order toperform 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 Double integralC :: 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
  • 13.
    In fact wecan'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
  • 14.
    f:: SF ab 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
  • 15.
  • 16.
    Space-Time Leak inFRP 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
  • 17.
    Space-Time Leak inFRP repeat x = λx → x : repeat x repeat 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`
  • 18.
    Space-Time Leak inFRP repeat x = λx → let xs = x:xs in xs repeat 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.
  • 19.
    Space-Time Leak inFRP 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.
  • 20.
    Space-Time Leak inFRP RECURSIVE CODES MAY DESTRUCT YOUR MIND
  • 21.
    Space-Time Leak inFRP A practical example: This example will show that space-time leak may happen when a computation is stateful ( based on time ) The Exponential Function
  • 22.
    Space-Time Leak inFRP 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 sampling type DTime = Double The later one will expand its second (C a) to make a continuing stream.
  • 23.
    Space-Time Leak inFRP And the integral functions: integralS :: Double → S Double → S Double integralS i (S f) = S (λdts → scanl (+) i (zipWith (∗) dts (f dts))) integralC :: Double → C Double → C Double integralC 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
  • 24.
    Space-Time Leak inFRP Remember that the integral need to accumulate each values at every DTime: DTime
  • 25.
    That's why itneed 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 Double integralC i (C p) = C (i, λdt→integralC (i + fst p ∗ dt) (snd p dt))
  • 26.
    That's why itneed 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 snd p
  • 27.
    That's why itneed 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 p snd p
  • 28.
    That's why itneed 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 p snd p
  • 29.
    Now we maywant 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
  • 30.
    Now we maywant 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
  • 31.
    run e, whererun (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
  • 32.
    run e, whererun (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
  • 33.
    ↪ 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
  • 34.
    Space-Time Leak inFRP 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
  • 35.
    Space-Time Leak inFRP And we can compare them by diagrams: f = λdt → integralC (1 + dt) (f dt) f = λdt → let x = integralC (1 + dt) x in x
  • 36.
    Space-Time Leak inFRP In the `repeat` example: repeat x = λx → x : repeat x repeat x = λx → let xs = x:xs in xs
  • 37.
    Space-Time Leak inFRP 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
  • 38.
    Space-Time Leak inFRP integralSF :: Double → SF Double Double integralSF i = SF (λx → (i, λdt → integralSF (i + dt ∗ x))) integralC :: Double → C Double → C Double integralC 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.
  • 39.
    Space-Time Leak inFRP integralSF :: Double → SF Double Double integralSF 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
  • 40.
    Back to Yampa Yampa& AFRP Signal Signal Function Space-Time Leak Arrow Arrow Syntax Event Switch Programming with Yampa Yeah ! Just escaped from the black hole !
  • 41.
    Yampa constructs thewhole system by combining Signal Functions Combine Signal Functions gf a b c h:: SF a c
  • 42.
    Yampa constructs thewhole system by combining Signal Functions Combine Signal Functions gf a b c ... and there's already a natural mechanism to achieve this in Haskell
  • 43.
    Yampa constructs thewhole system by combining Signal Functions Combine Signal Functions gf a b c ( ⋙):: SF a b → SF b c → SF a c "Composition" in Control.Arrow
  • 44.
    So Yampa isactually an AFRP framework, not only a FRP framework. Combine Signal Functions gf a b c ( ⋙):: SF a b → SF b c → SF a c "Composition" in Control.Arrow
  • 45.
  • 46.
    Arrow Basics "Unlike Monadand 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
  • 47.
    Arrow Basics In Monadthe 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
  • 48.
    Arrow Basics In Monadthe 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
  • 49.
    Arrow Basics Arrows, onthe 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
  • 50.
    Arrow Basics Arrows, onthe 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 You 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
  • 51.
    Arrow Basics So Arrowsare just like pipelines, and Monads are more like pipeline plus self-contained pumps
  • 52.
    There are manycombinators in Control.Arrow Arrow Basics Compose First f g f Split f g f Loop ( self-feedback )
  • 53.
    They will makeyour application like circuits Arrow Basics
  • 54.
    Back to Yampa Yampa& AFRP Signal Signal Function Space-Time Leak Arrow Arrow Syntax Event Switch Programming with Yampa Yampa www.haskell.org/ haskellwiki/Yampa (not official logo)
  • 55.
    Using Arrows withoutsome 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
  • 56.
    Using Arrows withoutsome 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
  • 57.
    In Yampa, Eventis just a Maybe-like type, representing discrete signals Event data Event a = NoEvent | Event a tag:: Event a → b → Event b We usually use events to trigger switchers, which will change the structure of our system dynamically.
  • 58.
    Arrows will makeyour 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
  • 59.
    This means circuitswon'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.
  • 60.
    A pseudo exampleshows how switches works Switches f Kin out System @ T0 1 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
  • 61.
    A pseudo exampleshows how switches works Switches f Kin out System @ T1 Then `K` put the new SF in our system. g System @ T2 Now we've 2 SFs and inputs will be dispatched to them all f Kin out g
  • 62.
    A pseudo exampleshows 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` g f Kin out System @ T4 Done. 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.
  • 63.
  • 64.
  • 65.
    Switches, more switches...( cry ) Switches http://www.haskell.org/haskellwiki/ Yampa/switch
  • 66.
    How is itpossible to use these stuff making a 3D game ? Switches
  • 67.
    Programming with Yampa Game Objects Route Killor Spawn dpSwitch Yampa Acrade http://bit.ly/sntw-ypa
  • 68.
    Game objects, likedragons, 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
  • 69.
    Game objects, likedragons, 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 ObjOutput From the file "Object.hs" in the game Frag.
  • 70.
    Object inputs inFrag: 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)]) }
  • 71.
    Object outputs inFrag: Game Objects data ObjOutput = ObjOutput { ooObsObjState :: !ObsObjState, ooSendMessage :: !(Event [(ILKey,(ILKey,Message))]), ooKillReq :: (Event ()), ooSpawnReq :: (Event [ILKey->Object]) }
  • 72.
    A simple gameobject 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
  • 73.
    A simple gameobject from Yampa Arcade Game Objects gun GameInput Mouse Position Mouse Button Keyboard Input Calculate new position and velocity of the gun SimpleGunState sgsPos sgsVel sgsFired Event
  • 74.
    In fact wedon'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 }
  • 75.
    This allow usto 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))
  • 76.
    Now we havegame 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.
  • 77.
    Now we havegame 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.
  • 78.
    Now we havegame objects. Then we've to use a `route` function to pair each object with an input. Route gun NPC#1 PC Wall R GameInput ObjOutput Decide which objects should be dispatched with current ObjOutputs
  • 79.
    Now we havegame 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 ) .
  • 80.
    Now we havegame 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.
  • 81.
    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.
  • 82.
    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 SFs 3. Spawn: with a function that will spawn new SFs The function will apply on object collections then change it
  • 83.
    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 ooKillReq Will 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
  • 84.
    Our `route` and`killOrSpwan` are prepared for the `dpSwitch`, which maintain whole dynamic structure in our program dpSwitch
  • 85.
    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)
  • 86.
    dpSwitch :: Functorcol ⇒ (∀ 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
  • 87.
    dpSwitch :: Functorcol ⇒ (∀ 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
  • 88.
    dpSwitch :: Functorcol ⇒ (∀ 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
  • 89.
    dpSwitch :: Functorcol ⇒ (∀ 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
  • 90.
    dpSwitch :: Functorcol ⇒ (∀ 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
  • 91.
    Now we havea dynamic structure can compose every piece in our game, so the Quake warrior can roar with firing guns, right ? Some Other Stuff
  • 92.
    Still Missing NPC &AI Levels OpenGL Binding ... There is no royal road to Haskell. —Euclid ( typeclassopedia )
  • 93.
    Maybe we're stillnot apt to create a 3D game with the AFRP framework, Yampa, but ideas in FRP & Arrow still inspire us. Conclusion
  • 94.
    And FRP isalso a generic way to construct any "Event- Driven" like system, from 3D FPS games to HTTP servers. Conclusion http://stackoverflow.com/questions/13486293/ is-frp-a-proper-way-to-implement-most-event-driven-things
  • 95.
    Some differences betweenFRP 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
  • 96.
    Some differences betweenFRP 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
  • 97.
    Conclusion This can beobvious 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 })
  • 98.
    Conclusion But some librariesalso 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)
  • 99.
    Conclusion But some librariesalso 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>
  • 100.
    Conclusion But some librariesalso try to implement FRP paradigm in JavaScript: http://elm-lang. org/learn/What-is-FRP. elm
  • 101.
    Conclusion And you maynotice 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
  • 102.
    References There's a lotof resources in Haskell Wiki http://www.haskell.org/haskellwiki/Yampa
  • 103.
    References Two useful articlesabout Yampa and game http://www.cse.unsw.edu.au/~pls/thesis/munc-thesis.pdf http://haskell.cs.yale.edu/wp-content/uploads/2011/01/yampa-arcade. pdf Yampa and robot control http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.5.2886 &rep=rep1&type=pdf
  • 104.