The Fran project has been carried out jointly by Microsoft Research and other Haskell researchers. Currently Fran runs under the Microsoft Windows '95/NT systems. This is research in progress; it is very likely that Fran will continue to change in the near future. We have tested all of the examples distributed with Fran but there are sure to be bugs in the current system. Please report any problems to fran-bugs@haskell.org. This document is associated with version 1.04 of Fran, distributed with Hugs 1.4. Newer versions of Fran will appear at http://www.research.microsoft.com/~conal/Fran. Information about the this version of Fran, including manuals and more animations, is at http://haskell.org/fran.
This manual contains a short introduction to Fran and an overview of the pre-defined types and functions available in Fran. A more detailed Fran tutorial is also included in the file tutorial.lhs.
All of the examples used in this manual are found in the hugs/lib/fran/demos/examples.hs. If you are unfamiliar with Fran, the best way to use this manual is to open (double click) examples.hs in the hugs/lib/fran/demos directory. An individual example can be executed using the run function: run n executes the nth example. The animation window may initially be hidden when you run an animation -- just click it on the task bar to make it visible. Terminate the animation by closing the animation window. Exit Hugs using :q when you are done. If you encounter a program error while an animation is running you may need to exit and restart Hugs. Running main displays all of the examples in sequence. Use the arrow keys to step through the examples.
Fran is based on two concepts: behaviors and events. A behavior is a value that varies over time; an event is an occurance at a specific time that generates a value. The interplay between behaviors and events is the essence of Fran. While this implementation of Fran is used for animations, the same model serves for other reactive systems. Indeed, this implementation cleanly separates the core behavior -- event interaction and the graphics library layered on top of it.
Since behaviors change over time, a behavior is observed by playing it
in some manner. That is, the user watches and listens to an object as
it changes and reacts to input. Fran includes functions for
constructing animations that are played in a graphics window.
Full details of this library are presented later; here we will introduce
just enough of it so that we can explore events and behaviors. Here
is a small subset of the graphics library:
-- Basic data types
data Color -- data type of colors
type RealVal = Double
type Time = Double
data Point2 = -- a 2D point
data Vector2 = -- a 2D vector
data ImageB -- Reactive images
-- synonyms that abbreviate common behavioral types
type RealB = Behavior RealVal
type ColorB = Behavior Color
type Point2B = Behavior Point2
type Vector2B = Behavior Point2
type TimeB = Behavior Time
-- Graphics operations
-- A behavioral point constructor
point2XY :: RealB -> RealB -> Point2B -- Construct a 2D point
vector2XY :: RealB -> RealB -> Vector2B -- Construct a 2D vector
origin2 :: RealB -- The origin (maps to screen center)
circle :: ImageB -- A circle at (0,0) with unit radius
withColor :: ColorB -> ImageB -> ImageB -- Paint with a solid color
move :: Vector2B -> ImageB -> ImageB -- Move an image
red, blue,green :: ColorB -- Some built-in colors
over :: ImageB -> ImageB -> ImageB -- Place one image over another
bigger :: RealB -> ImageB -> ImageB -- Enlarge (or reduce) the
-- size of an image
-- Display routine. Initial screen scaled to (-1,-1) , (1,1)
disp :: (User -> ImageB) -> IO () -- display an image behavior
To avoid clutter in type signatures involving Behavior many types
have pre-defined synonyms for their behavioral
counterparts. The type declarations above show some of these
synonyms. Some behavioral types, such as ImageB, are implemented
directly instead of using the Behavior type constructor.
The disp function takes a reactive animation and plays it in a graphics window. Fran uses the type User to represent external events generated by the user. Images which don't react user input can usually ignore the User value that disp passes to the animation.
Here are a few of the built-in behaviors in Fran:
time :: TimeB
constantB :: a -> Behavior a -- create a constant behavior
mouseMotion :: User -> Vector2B -- tracks the position of the mouse
Here is a very simple program to display a pulsing circle:
module Examples where
import Fran -- Basic Fran functionality
circ :: ImageB
circ = bigger (sin time) (withColor red circle)
example1 u = circ
Execute this example using either disp example1 or run 1, for short.
The Fran module is found on the standard Hugs search path.
Here is a slightly more complex behavior:
ball1, ball2, movingBall1, movingBall2 :: ImageB
ball1 = bigger 0.3 (withColor red circle)
movingBall1 = move (vector2XY 0 wiggle) ball1
ball2 = bigger 0.4 (withColor blue circle)
movingBall2 = move (vector2XY wiggle 0) ball2
example2 u = movingBall1 `over` movingBall2
Some behaviors are generated by user interaction. For example,
the mouse motion is represented by the following behavior:
mouseMotion :: User -> Vector2B
As mouse motion is part of the user input, the User value
passed into the animation by the disp function must then be passed
on to mouseMotion. This program displays a ball which follows the
mouse:
example3 u = move (mouseMotion u) ball1
Events are grouped into event streams. Every occurance of an event
yields both a value and a new event containing
the remainder of the stream. Thus a value of type Event T
denotes a series of events, each generating a value of type T. The type
Event T can be understood as [(Time,T)]: a (sorted) list of occurances
containing the time and event value for each occurance. For example,
this list represents a possible sequence of keyboard events:
[(1,'a'), (3,'b'), (7,'c')]
The residual stream of events yielded by an event occurance is the
aged event. In the above example, after the first key event is
detected at time 1, the aged event stream becomes:
[(3,'b'), (7,'c')]
The next keypress event at time 3 must be obtained from the aged event
stream.
A Fran program reacts to external events; each kind of external
event is represented by constructor in the data type UserAction.
The stream of incoming events has the type Event UserAction. This
synonym:
type User = Event UserAction
gives a shorter name to the program input. Specific kinds of
events, such as `resize window' or `keyboard press' are extracted
from the User type. For example, these events are associated with
the mouse buttons:
lbpU,rbpU :: User -> Event (User) -- Mouse button presses
These events yield a new User value which holds the aged user event
stream. (The lbpU and rbpU functions are not formally a part of
Fran but can be defined trivially from built in Fran functions.)
Here are some other basic events:
neverE :: Event a
constE :: Time -> a -> Event a
timeIs :: Time -> Event ()
timeIs t = constE () t
alarmE :: Time -> Time -> Event ()
The neverE event never happens. The constE and timeIs events
have a given occurence time; when aged they become neverE. The
alarmE event goes off at regular intervals: the arguments are the
start time and the time between events.
Before we can use untilB in an example, we need to transform an
event such as lbpU of type Event User into an event which generates
a behavior. This function transforms an event:
(==>) :: Event a -> (a -> b) -> Event b
Note the similarity between (==>) and the map function.
Using (==>), we can now write a simple reactive behavior:
example4 u = withColor (doRed u) circle where
doRed, doBlue :: User -> ColorB
doRed u = red `untilB` (lbpU u ==> doBlue)
doBlue u = blue `untilB` (lbpU u ==> doRed)
The circle changes between red and blue with each left button
press. The parenthesis around the ==> expressions have been added
for clarity; they are not needed since `untilB` has a lower fixity
than ==> . The (==>) operator passes the new User value
generated by lbp on to the next cycle.
The (==>) operator is a special case of a more general event
handler, handleE. Using handleE, the event time, event value, and
next event in the event stream are all revealed.
handleE :: Event a -> (Time -> a -> Event a -> b) -> Event b
(==>) :: Event a -> (a -> b) -> Event b
e ==> f = e `handleE` (\_ x _ -> f x)
(-=>) :: Event a -> b -> Event b
e -=> v = e ==> const v
withRestE :: Event a -> Event (a, Event a)
withRestE e = e `handleE` (\_ v e' -> (v,e'))
withTimeE :: Event a -> Event (a, Time)
withTimeE e = e `handleE` (\t v _ -> (v,t))
nextE :: Event a -> Event (Event a)
nextE e = e `handleE` \ te x e' -> e'
These functions associate the events in an event stream with values in
a list:
withElemE :: Event a -> [b] -> Event (a,b)
withElemE_ :: Event a -> [b] -> Event b
e `withElemE_` l = (e `withElemE` l) ==> snd
Finally, these utilities convert event streams into behaviors:
-- Assemble a behavior piecewise from a series of events
switcher :: GBehavior bv => bv -> Event bv -> bv
switcher b0 e = b0 `untilB` (e `handleE` (\_ e' b' -> switcher b' e'))
-- A switcher for constant behaviors
stepper :: a -> Event a -> Behavior a
stepper x0 e = switcher (constantB x0) (e ==> constantB)
This example uses withElemE_ to map the left button presses onto a
series of numbers then uses stepper to convert the event series into
a behavior.
example6 u = move (vector2XY l l) ball
where
ball = bigger 0.3 (withColor red circle)
l = stepper 0 (lbpCounter u)
lbpCounter :: User -> Event (RealVal)
lbpCounter u = withElemE_ (lbpU u) [0.1, 0.2 ..]
These functions filter a selected set of events out of an event stream:
filterE :: Event a -> (a -> Maybe b) -> Event b
suchThat :: Event a -> (a -> Bool) -> Event a
suchThat ev pred = filterE ev (\a -> if pred a then Just a else Nothing)
For example, the event which recognizes a particular character is:
keyPress :: VKey -> Event -> Event User
keyPress k u = keyPressAny u `filterE` (\(k',u') ->
if k == k' then Just u' else Nothing
The User argument supplies the integration start time and a sampling clock which determines the step size used by the underlying numerical method.
This example uses integration to express the motion of a falling ball:
example7 u = withColor red (moveXY 0 (position u) (bigger 0.1 circle))
where
gravity :: RealB
gravity = -0.1
velocity, position :: RealB
velocity u = integral gravity u
position u = integral (velocity u)
Integrals may be mutually recursive.
The renaming required by && can sometimes be avoided using type
classes. For example, an instance declaration such as the following
instance Num a => Num (Behavior a)
allows all of the methods in Num to be applied directly to behaviors
without renaming. Constant types in the class definition cannot be
lifted by such a declaration. In the Num instance
above, the type of fromInteger is
fromInteger :: Num a => Integer -> (Behavior a)
The argument to fromInteger is not lifted - only the result. This
allows integer constants to be treated as constant behaviors. While
fromInteger works in the expected way, other class methods cannot be
used. In the declaration
instance Ord a => Ord (Behavior a)
is not useful since it defines operations such as
(>) :: Behavior a -> Behavior a -> Bool
Unfortunately, Fran needs a > function which returns Behavior Bool
instance of just Bool. The Eq and Ord classes are not lifted
using instance declarations. Rather, each method is individually
renamed and lifted.
These are the lifting functions: they transform a non-behavioral
function into its behavioral counterpart:
constantB :: a -> Behavior a
($*) :: Behavior (a -> b) -> Behavior a -> Behavior b
lift0 :: a -> Behavior a
lift0 = constantB
lift1 :: (a -> b) -> Behavior a -> Behavior b
lift1 f b1 = lift0 f $* b1
lift2 :: (a -> b -> c) -> Behavior a -> Behavior b -> Behavior c
lift2 f b1 b2 = lift1 f b1 $* b2
lift3 :: (a -> b -> c -> d)
-> Behavior a -> Behavior b -> Behavior c -> Behavior d
lift3 f b1 b2 b3 = lift2 f b1 b2 $* b3
...
lift7 ...
Using these functions, the definition of (>*) is
(>*) = lift2 (>)
Many Prelude functions have been lifted in Fran via overloading:
(+) :: Num a => Behavior a -> Behavior a -> Behavior a
(*) :: Num a => Behavior a -> Behavior a -> Behavior a
negate :: Num a => Behavior a -> Behavior a
abs :: Num a => Behavior a -> Behavior a
fromInteger :: Num a => Integer -> Behavior a
fromInt :: Num a => Int -> Behavior a
quot :: Integral a => Behavior a -> Behavior a -> Behavior a
rem :: Integral a => Behavior a -> Behavior a -> Behavior a
div :: Integral a => Behavior a -> Behavior a -> Behavior a
mod :: Integral a => Behavior a -> Behavior a -> Behavior a
quotRem :: Integral a => Behavior a -> Behavior a ->
(Behavior a, Behavior a)
divMod :: Integral a => Behavior a -> Behavior a ->
(Behavior a, Behavior a)
fromDouble :: Fractional a => Double -> Behavior a
fromRational :: Fractional a => Rational -> Behavior a
(/) :: Fractional a => Behavior a -> Behavior a -> Behavior a
sin :: Floating a => Behavior a -> Behavior a
cos :: Floating a => Behavior a -> Behavior a
tan :: Floating a => Behavior a -> Behavior a
asin :: Floating a => Behavior a -> Behavior a
acos :: Floating a => Behavior a -> Behavior a
atan :: Floating a => Behavior a -> Behavior a
sinh :: Floating a => Behavior a -> Behavior a
cosh :: Floating a => Behavior a -> Behavior a
tanh :: Floating a => Behavior a -> Behavior a
asinh :: Floating a => Behavior a -> Behavior a
acosh :: Floating a => Behavior a -> Behavior a
atanh :: Floating a => Behavior a -> Behavior a
pi :: Floating a => Behavior a
exp :: Floating a => Behavior a -> Behavior a
log :: Floating a => Behavior a -> Behavior a
sqrt :: Floating a => Behavior a -> Behavior a
(**) :: Floating a => Behavior a -> Behavior a -> Behavior a
logBase :: Floating a => Behavior a -> Behavior a -> Behavior a
These operations correspond to functions which cannot be overloaded
for behaviors. The convention is to use the B suffix for vars and a
* suffix for ops.
fromIntegerB :: Num a => IntegerB -> Behavior a
toRationalB :: Real a => Behavior a -> Behavior Rational
toIntegerB :: Integral a => Behavior a -> IntegerB
evenB, oddB :: Integral a => Behavior a -> BoolB
toIntB :: Integral a => Behavior a -> IntB
properFractionB :: (RealFrac a, Integral b) => Behavior a -> Behavior (b,a)
truncateB :: (RealFrac a, Integral b) => Behavior a -> Behavior b
roundB :: (RealFrac a, Integral b) => Behavior a -> Behavior b
ceilingB :: (RealFrac a, Integral b) => Behavior a -> Behavior b
floorB :: (RealFrac a, Integral b) => Behavior a -> Behavior b
(^*) :: (Num a, Integral b) =>
Behavior a -> Behavior b -> Behavior a
(^^*) :: (Fractional a, Integral b) =>
Behavior a -> Behavior b -> Behavior a
(==*) :: Eq a => Behavior a -> Behavior a -> BoolB
(/=*) :: Eq a => Behavior a -> Behavior a -> BoolB
(<*) :: Ord a => Behavior a -> Behavior a -> BoolB
(<=*) :: Ord a => Behavior a -> Behavior a -> BoolB
(>=*) :: Ord a => Behavior a -> Behavior a -> BoolB
(>*) :: Ord a => Behavior a -> Behavior a -> BoolB
cond :: BoolB -> Behavior a -> Behavior a -> Behavior a
notB :: BoolB -> BoolB
(&&*) :: BoolB -> BoolB -> BoolB
(||*) :: BoolB -> BoolB -> BoolB
pairB :: Behavior a -> Behavior b -> Behavior (a,b)
fstB :: Behavior (a,b) -> Behavior a
sndB :: Behavior (a,b) -> Behavior b
pairBSplit :: Behavior (a,b) -> (Behavior a, Behavior b)
showB :: (Show a) => Behavior a -> Behavior String
A few list-based functions are lifted, although most of the functions
in PreludeList are not lifted.
nilB :: Behavior [a]
consB :: Behavior a -> Behavior [b] -> Behavior [b]
headB :: Behavior [a] -> Behavior a
tailB :: Behavior [a] -> Behavior [a]
liftLs :: [Behavior a] -> Behavior [a]
Read the `.' in the operators above as `point' and `^' as `vector'. Thus .+^ means `point plus vector'.
These are the transformation operations:
class Tranformable2B a where
(*%) :: Transform2B -> a -> a -- Applies a transform
identity2 :: Transform2B
translate2 :: Vector2B -> Transform2B
rotate2 :: RealB -> Transform2B
compose2 :: Transform2B -> Transform2B -> Transform2B
inverse2 :: Transform2B -> Transform2B
uscale2 :: RealB -> Transform2B -- only uniform scaling
factorTransform2 :: Transform2B -> Behavior (Vector2, RealVal, RealVal)
instance Transformable2B Point2B
instance Transformable2B Vector2B
class Tranformable3B a where
(**%) :: Transform3B -> a -> a -- not currently used
class Translateable3B a where
translate3 :: a -> Transform3
class Scaleable3B a where
scale3 :: a -> Transform3
identity3 :: Transform3B
rotate3 :: Vector3B -> RealB -> Transform3B
compose3 :: Transform3B -> Transform3B -> Transform3B
uscale3 :: RealB -> Trabsform3
factorTransform3 :: Transform3B -> Behavior (Vector3, RealVal, RealVal)
instance Translatable3B Point3B
instance Translatable3B Vector3B
instance Scaleable3B Point3B
instance Scaleable3B Vector3B
A transformation which doubles the size of an object and then rotates
it 90 degrees would be rotate2 (pi/2) `compose2` uscale2 2. Note that
the first transform applied is the one on the right, as with Haskell's
function composition operator (.).
Bitmaps are centered at the origin and are displayed actual size unless scaled. Bitmaps must be stored in .bmp files.
These are some simple utilities for 2-D images. The Transformable2B context denotes images as well as 2-D points and vectors.
-- Star figure. Arguments: skip and vertices.
star :: IntB -> IntB -> ImageB
regularPolygon :: IntB -> ImageB
regularPolygon vs = star 1 vs
square :: ImageB
square = regularPolygon 4
move :: Transformable2B a => Vector2B -> a -> a
move dp thing = translate2 dp *% thing
moveXY :: Transformable2B a => RealB -> RealB -> a -> a
moveXY dx dy thing = move (vector2XY dx dy) thing
bigger, smaller :: RealB -> ImageB -> ImageB
bigger sc = (uscale2 sc *%)
smaller sc = bigger (1/sc)
stretch :: RealB -> ImageB -> ImageB
stretch = bigger
-- 1.0 = 180 degrees
turnLeft, turnRight :: Transformable2B a => FractionB -> a -> a
turnLeft frac im = rotate2 (frac * pi) *% im
turnRight frac = turnLeft (-frac)
-- Oscillates between -1 and 1
wiggle, waggle :: RealB
wiggle = sin (pi * time)
waggle = cos (pi * time) -- 90 degrees out of phase
wiggleRange :: RealB -> RealB -> RealB
wiggleRange lo hi = lo + (hi-lo) * (wiggle+1)/2
stringBIm :: Behavior String -> ImageB
stringIm :: String -> ImageB
stringBIm str = textImage (simpleText str)
stringIm = stringBIm . constantB
showBIm :: Show a => Behavior a -> ImageB
showIm :: Show a => a -> ImageB
showBIm = stringBIm . showB
showIm = showBIm . constantB
-- Given an image and a canonical size, stretch the image uniformly so
-- that the size maps exactly onto the window view size.
viewStretch :: Vector2B -> User -> ImageB -> ImageB
silence :: SoundB
mix :: SoundB -> SoundB -> SoundB
volume :: RealB -> SoundB -> SoundB
pitch :: RealB -> SoundB -> SoundB
instance GBehavior SoundB