Title: How FRP Gets Its Core
1How FRP Gets Its Core
Official Graduate Student Talk
- Design Decisions andImplementation
Techniquesfor Functional Reactive Programming
Zhanyong Wan10/31/2000 Yale University Advisor
Paul Hudak
2Outline
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
3What Is FRP?
- Functional Reactive Programming (FRP)
- General framework for reactive hybrid systems
- Interactive animation -- Fran
- Robotics -- Frob
- Soccer -- RoboCup
- GUI -- FranTk, Frappe
- Other control systems -- Fr
- Functional
- Higher-order functions
- Polymorphism
- Static typing
- Pure
- High-level, declarative
- Time is continuous
- Core FRP
- Attempt to unify the dialects
4Understand FRP
- Behaviors
- Reactive, continuous-time-varying values
- time, temperature, sin time
- Events
- Streams of discrete event occurrences
- lbp, when (temperature gt threshold)
- Switching
- sin time until when (temperature gt threshold)
-gt cos time - Combinators
- Behaviors and events are both first-class.
- when Behavior Bool -gt Event ()
- until Behavior a -gt Event (Behavior a) -gt
- Behavior a
- (-gt) Event a -gt b -gt Event b
5A Discrete Implementation
- Domain-specific language (DSL) embedded in
Haskell - Library of Haskell data types and functions
- Signals as stream transformers
- type Behavior a UserAction -gt Time -gt a
- type Event a UserAction -gt Time -gt Maybe
a - Combinators as higher-order functions on signals
- until Behavior a -gt Event (Behavior a) -gt
Behavior a - (-gt) Event a -gt b -gt Event b
- Run-time engine
FRP program
input streams
behavior
Run-timeEngine
behavior
result streams
event
6Outline
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
7Legend
- Design decisions
- Implementation techniques
D
I
8Clocked Events -- Motivation
D
- Motivation
- We can apply lifted functions to behaviors
- lift2 (a -gt b -gt c) -gt Behavior a -gt Behavior
b -gt Behavior c - lift2 () b1 b2 ?
- We can not apply lifted functions to events
- lift2 () e1 e2 ?
- e1 and e2 are not guaranteed to be synchronous
9Clocked Events -- Implementation
I
- Clocked events
- newtype CEvent c a
- CEvent (UserAction -gt Time -gt Maybe a)
- Two events w/ same clock are guaranteed to be
synchronous - lift2 (a -gt b -gt c) -gt CEvent clk a -gt
- CEvent clk b -gt CEvent clk c
- Events w/ different clocks are incompatible
- e1 CEvent C1 Int
- e2, e3 CEvent C2 Int
- lift2 () e1 e2 ?
- lift2 () e2 e3 ?
10Ambiguously Clocked Events
SPAM
D
- Const behaviors
- lift0 a -gt Behavior a
- b1, b2 Behavior Int
- b1
- b2 lift2 () b1 (lift0 5)
- Const clocked events
- e1, e2 CEvent C Int
- e3, e4 CEvent C Int
- e1
- e2 lift2 () e1 (lift0 5)
- e3
- e4 lift2 () e3 (lift0 5)
- lift0 produces an ambiguously clocked event
- lift0 a -gt CEvent clk a
- newtype CEvent c a
- CEvent (UserAction -gt Time -gt Maybe a)
- AmbCEvent a
11Overloading Lifting Combinators
I
- Lifting should be overloaded
- for behaviors
- for clocked events
- Haskell type class provides nice support for
overloading - class Zippable z where
- lift0 a -gt z a
- lift1 (a-gtb) -gt z a -gt z b
- lift2 (a-gtb-gtc) -gt z a -gt z b -gt z c
-
- instance Zippable Behavior where
- instance Zippable (CEvent clk) where
12Outline
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
13User Aging the Question
D
- Question What should a mean?
- keyCount 0 stepAccum key -gt (1)
- a 0 until lbp -gt keyCount
- Answer 1 only key presses after lbp count.
- Answer 2 all key presses count.
- Which answer do we want to be correct?
14User Aging the Question
D
- Question What should a mean?
- keyCount 0 stepAccum key -gt (1)
- a 0 until lbp -gt keyCount
- Answer 1 only key presses after lbp count.
- Answer 2 all key presses count.
- Which answer do we want to be correct?
- Both!
15Explicit User Aging
D
- Age user explicitly
- User is the stream of user actions
- Make user an explicit parameter
- key User -gt Event Char
- lbp User -gt Event ()
- When switching, the after-behavior receives an
aged user - The programmer decides to age the user or not
- keyCount u 0 stepAccum key u -gt (1)
- a1 u 0 until lbp u -gt (\u -gt keyCount u)
- a2 u 0 until lbp u -gt (\u -gt keyCount u)
- Used by Conal Elliott in Fran
u original user
u aged user
16Problems of the Explicit Approach
D
- Tedious
- Space leak
- User is a data structure representing all user
inputs - The program can hold on to the original User
- a2 u 0 until lbp u -gt (\u -gt keyCount u)
- Time leak
- Catch-up computation
- Unbounded response time
- Not good enough!
17Implicit User Aging
D
- User no longer explicit
- Age User implicitly
- until always ages the User
- a1 0 until lbp -gt keyCount
- No space leak
- No time leak
- But what if we dont want to age the User?
18Implicit User Aging (contd)
D
- What if we dont want to age the User?
- Solution 1
- keyCount n n stepAccum key -gt (1)
- a2 0 until (lbp snapshot_ keyCount 0) gt
(\n -gt keyCount n) - Obscured code
- Not expressive enough
19Implicit User Aging (contd)
- What if we dont want to age the User?
- Solution 2
- a2 letrun keyCount 0 stepAccum key -gt (1)
- in 0 until lbp -gt keyCount
- FRP is embedded in Haskell
- Can not define construct that introduces binding
letrun b foo in barb
runningIn Behavior a -gt (Behavior a -gt
Behavior b) -gt Behavior b a2 (0
stepAccum key -gt (1)) runningIn
(\keyCount -gt 0 until lbp -gt keyCount)
20Outline
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
21Implementation of runningIn
I
- A behavior/event/cevent can run in a
behavior/event/cevent - 33 9 combinations!
- runningIn needs to be overloaded
- Multi-parameter type class
- class Run a b where
- runningIn a -gt (a -gt b) -gt b
- instance Run Behavior Behavior where
- instance Run Behavior Event where
- instance Run Behavior (CEvent c) where
-
- instance Run (CEvent c) (CEvent c) where
- 9 instance declarations
22Implementation of runningIn (contd)
I
- Not neat
- n2 instances are too many
- What if we add a new kind of signal?
- An Improvement
- class ImpAs a b a -gt b where
- -- a can be implemented as b
- toImp a -gt b
- fromImp b -gt a
- type GB a UserAction -gt Time -gt a
- instance ImpAs (Behavior a) (GB a) where
- instance ImpAs (Event a) (GB (Maybe a)) where
- instance ImpAs (CEvent clk a) (GB (Maybe a))
where - instance (ImpAs a (GB b), ImpAs c (GB d)) gt Run
a c where - Now only (n 1) instances
23Outline
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
24Input in Original FRP
D
- User input type hard-wired
- type UserAction
- type Behavior a UserAction -gt Time -gt a
- type Event a UserAction -gt Time -gt Maybe
a - Type UserAction depends on the application domain
- Animation and GUI
- UserAction mouse movement mouse clicks
keyboard clicks - Robotics
- UserAction vision bumper reading reading
of other sensors - Other domains
- UserAction
25Problems of the User Input System
D
- When domain changes, UserAction needs to be
changed - Source code of FRP to be edited!
- Hard to compose different domains
- A robot which receives commands from keyboard
- All parts of a system must have the same
UserAction type
26The New Input System
D
- Solution
- Make signals parameterized over the input type
- type Behavior inp a inp -gt Time -gt a
- type Event inp a inp -gt Time -gt Maybe a
- Use type classes to structure multiple input
types
27The New Input System
I
- Type classes
- class GuiInput gin where
- lbp Event gin ()
- mouse Behavior gin Point2
- class ConInput cin where
- key Event cin Char
- Code not using input is polymorphic over input
- time Behavior inp Time
- ball Behavior inp Picture
- ball moveXY (sin time) (cos time) circle
- Code using input is constrained w/ context
- kick GuiInput gin gt Behavior gin Real
- kick 0 until lbp -gt integral 1
28The New Input System (contd)
D
- Inputs can be combined
- Compose input types
- class (GuiInput in, ConInput in) gt GCInput in
- instance (GuiInput in, ConInput in) gt GCInput in
- Have different input types in one system
- foo Behavior KeyboardInp Real
- bar Behavior VisionInp Bool
- Core FRP no longer tided to any particular input
type - User chooses the input module(s) he wants
- import CoreFRP
- import Graphics -- Instance of GuiInput and
ConInput - import Vision -- Instance of VisionInput
29Outline
SPAM
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
30Memoization
SPAM
I
- Motivation
- Behaviors are implemented as functions
- type Behavior inp a inp -gt Time -gt a
- Multiple occurrences of a behavior mean
duplicated computation - sqrB b lift2 () b b
- Even worse for recursive behaviors
- v v0 k integral v
- Memoization
- Needed when a behavior is used more than once
- sqrB b let b memo b in lift2 () b b
- Or for recursive behaviors
- v memo (v0 k integral v)
31Memoization (contd)
SPAM
I
- Transparent memoization
- Implementation detail
- Shift memo into the definition of combinators
- Just a hack
- Sometimes the programmer may need to insert memo
- Full transparency requires a compiler
- Side-effect
- Linear space leak
- Solution Time bomb!
- Plant an action in the result stream to flush the
cache - A hack may fail to seal the leak
- Better let GC flush the cache
- Requires a hook to the Haskell GC
- Easier when we compile
32Outline
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
33Task
D
- Task
- type Task b e (Behavior b, Event e)
- Why ?
- Behaviors and events are too low-level.
- Program is like a network
- Connections as data dependency
- s lift2 () a b
- Sequential temporal composition as switchers
- a b until (c -gt d)
- Continuation d must be supplied
- We want a construct that involves only b and c
34Task Composition
I
- Composing tasks
- Sequential
- (gtgtgt) Task b x -gt (x -gt Task b y) -gt Task b y
- (b, e) gtgtgt f
- (b until e gt (fst . f), e andThen (snd .
f)) - andThen Event x -gt (x -gt Event y) -gt Event y
- Parallel
- () Task a x -gt Task b x -gt Task (a,b) x
35Task Is a Monad
D
- Monad
- class Monad m where
- (gtgt) m a -gt (a -gt m b) -gt m b
- return a -gt m a
- Task is a monad!
- Bind (gtgt) is gtgtgt
- return is instantaneous task
- return x (undefined, nowE x)
36Why Monad
D
- Nicer syntax
- do x lt- task1
- task2 x
- task3
- compared with
- task1 gtgtgt
- (\x -gt task2 x gtgtgt
- (\_ -gt task3))
- Easier to understand
- Libraries on monads ready to use
- Expandable
37But Wait!
SPAM
I
- Task is a monad or really?
- Implementation of Task
- (b, e) gtgt f
- (b until e gt (fst . f), e andThen (snd .
f)) - return x (undefined, nowE x)
- Inspect the first law
- return x gtgt f
- (undefined until nowE (fst (f x)), snd (f x))
- f x (fst (f x), snd (f x))
- undefined until nowE b b ?
- Monadic Laws
- return x gtgt f f x
- m gtgt return m
- (associativity)
38Inside until
SPAM
I
c
tick
k
d
tick
k
buntilc-gtd
tick
k
39undefined until nowE b vs. b
SPAM
I
undefined
?
tick
nowE
tick
b
tick
undefineduntil nowE b
b
?
?
tick
tick
40Task Is Not a Monad (Yet)
SPAM
I
- Law 1 is violated
- until has a one-step delay
- b until c -gt d
- d starts one tick after c occurs
- Law 2 is violated too
- Why until introduces delay?
- Recursive definition
- a b until (a gt 3) -gt d
- In a b until c -gt d, where c refers to a,
the value of a at tick t cannot depend on the
value of c at tick t
41Attempt 1 Switch Instantaneously
SPAM
I
b
tick
k
c
tick
k
42Attempt 2 Start Now, Switch Later
SPAM
I
c
tick
k
?
43Beyond Simple Tasks
D
- Need for extending simple tasks
- Advanced tasks
- Parallel tasks
- Interruptible tasks
- Environment tasks
- State tasks
- Message tasks
- etc
44Message Tasks
SPAM
D
- Message Task
- type MTask m b e (Event m, Behavior b, Event
e) - send m -gt MTask m b ()
- send m (nowE m, undefined, nowE ())
- Example
- do send Phase 1 starts
- task1
- send Phase 1 ends
- send Phase 2 starts
- task2
- send Phase 2 ends
45Mixing until
SPAM
I
f
c
tick
k
mUntil f b (c-gtd)
tick
k
46Message Tasks -- Implementation
SPAM
I
- Implementation
- return e -gt MTask m b e
- return x (neverE, undefined, nowE x)
- (gtgt) MTask m b x -gt (x -gt MTask m b y)
- -gt MTask m b y
- (m,b,e) gtgt f
- (mUntil () m (e gt (fst3 . f)),
- b until (e gt (snd3 . f)),
- e andThen (thd3 . F)
- )
47Structure the Task Hierarchy
I
- Monolithic type doesnt scale
- Error-prone
- Hard to extend
- Task transformers
- A type function that maps a Task type to another
Task type - Similar to Monad transformers
- Idea
- Factor a complex Task type into orthogonal
functional units - Each unit is a Task transformer
- Compose functionalities via composing Task
transformers - Pros/Cons of Task transformers
- Modular
- Scalable
- A little overhead
48Task Transformers
I
-
- -- the Generic Task class
- class GTask t where
- mkTask Behavior b -gt Event e -gt t b e
- foreverT Behavior b -gt t b e
-
- -- base case
- instance GTask Task where
- -- the state task transformer
- newtype StateT s t b x
- StateT (s -gt t b (s, x))
- instance GTask t gt GTask (StateT s t) where
49Task Transformers (contd)
I
-
- -- the environment task transformer
- newtype EnvT env t b x
- EnvT (env -gt t b x)
- instance GTask t gt GTask (EnvT env t) where
- -- using task transformers
- type STask s StateT s Task
- type ESTask env s EnvT env (STask s)
50Outline
- Background
- Design implementation of core FRP
- Clocked events
- User implicit vs. explicit
- Sustainment
- Modular input
- Memoization
- Task monad
- Future work
51Future Work
- Foundation
- Clock calculus
- Refined type system
- Semantics
- Design
- Feedback from real world
- Completeness
- Implementation
- Optimization
- Preprocessing
- Compilation
52Future Work
SPAM
- Optimization
- Const, Stateless and Stateful
- type Behavior inp a
- ConstB a
- StatelessB (inp -gt Time -gt a)
- StatefulB (inp -gt Time -gt a)
- Piecewise-constant behaviors
- Better memoization
- Memoization analysis
- When to memoize, when not to
- Space leak
- GC
53Future Work (contd)
SPAM
- Better sustainment
- Problems of runningIn
- Expensive
- Doesnt mix w/ recursion
- Too eager
- Better implementation for runningIn
- Garbage collection
- A construct better than runningIn?
54Future Work (contd)
SPAM
- Foundation
- Clock calculus
- More refined type system
- runningIn is a restricted Monad
- Sub-typing
- Sustained signals
- Const/stateless/stateful signals
- Implementation
- Preprocessing
- Compilation
55Thank You, and Happy Halloween!