active-0.2.0.13: Abstractions for animation

Data.Active

Description

Inspired by the work of Kevin Matlage and Andy Gill (Every Animation Should Have a Beginning, a Middle, and an End, Trends in Functional Programming, 2010. http://ku-fpg.github.io/files/Matlage-10-BeginningMiddleEnd.pdf), this module defines a simple abstraction for working with time-varying values. A value of type Active a is either a constant value of type a, or a time-varying value of type a (i.e. a function from time to a) with specific start and end times. Since active values have start and end times, they can be aligned, sequenced, stretched, or reversed.

In a sense, this is sort of like a stripped-down version of functional reactive programming (FRP), without the reactivity.

The original motivating use for this library is to support making animations with the diagrams framework (http://projects.haskell.org/diagrams), but the hope is that it may find more general utility.

There are two basic ways to create an Active value. The first is to use mkActive to create one directly, by specifying a start and end time and a function of time. More indirectly, one can use the Applicative instance together with the unit interval ui, which takes on values from the unit interval from time 0 to time 1, or interval, which creates an active over an arbitrary interval.

For example, to create a value of type Active Double which represents one period of a sine wave starting at time 0 and ending at time 1, we could write

mkActive 0 1 (\t -> sin (fromTime t * tau))

or

(sin . (*tau)) <$> ui pure can also be used to create Active values which are constant and have no start or end time. For example, mod <$> (floor <$> interval 0 100) <*> pure 7 cycles repeatedly through the numbers 0-6. Note that the "idiom bracket" notation supported by the SHE preprocessor (http://personal.cis.strath.ac.uk/~conor/pub/she/, http://hackage.haskell.org/package/she) can make for somewhat more readable Applicative code. For example, the above example can be rewritten using SHE as {-# OPTIONS_GHC -F -pgmF she #-} ... (| mod (| floor (interval 0 100) |) ~7 |) There are many functions for transforming and composing active values; see the documentation below for more details. With careful handling, this module should be suitable to generating deep embeddings if Active values. Synopsis # Representing time ## Time and duration data Time n # An abstract type for representing points in time. Note that literal numeric values may be used as Times, thanks to the the Num and Fractional instances. Instances  # Methodsfmap :: (a -> b) -> Time a -> Time b #(<$) :: a -> Time b -> Time a # # Associated Typestype Diff (Time :: * -> *) :: * -> * # Methods(.-.) :: Num a => Time a -> Time a -> Diff Time a #(.+^) :: Num a => Time a -> Diff Time a -> Time a #(.-^) :: Num a => Time a -> Diff Time a -> Time a # Enum n => Enum (Time n) # Methodssucc :: Time n -> Time n #pred :: Time n -> Time n #toEnum :: Int -> Time n #fromEnum :: Time n -> Int #enumFrom :: Time n -> [Time n] #enumFromThen :: Time n -> Time n -> [Time n] #enumFromTo :: Time n -> Time n -> [Time n] #enumFromThenTo :: Time n -> Time n -> Time n -> [Time n] # Eq n => Eq (Time n) # Methods(==) :: Time n -> Time n -> Bool #(/=) :: Time n -> Time n -> Bool # Fractional n => Fractional (Time n) # Methods(/) :: Time n -> Time n -> Time n #recip :: Time n -> Time n # Num n => Num (Time n) # Methods(+) :: Time n -> Time n -> Time n #(-) :: Time n -> Time n -> Time n #(*) :: Time n -> Time n -> Time n #negate :: Time n -> Time n #abs :: Time n -> Time n #signum :: Time n -> Time n # Ord n => Ord (Time n) # Methodscompare :: Time n -> Time n -> Ordering #(<) :: Time n -> Time n -> Bool #(<=) :: Time n -> Time n -> Bool #(>) :: Time n -> Time n -> Bool #(>=) :: Time n -> Time n -> Bool #max :: Time n -> Time n -> Time n #min :: Time n -> Time n -> Time n # Read n => Read (Time n) # MethodsreadsPrec :: Int -> ReadS (Time n) #readList :: ReadS [Time n] # Real n => Real (Time n) # MethodstoRational :: Time n -> Rational # RealFrac n => RealFrac (Time n) # MethodsproperFraction :: Integral b => Time n -> (b, Time n) #truncate :: Integral b => Time n -> b #round :: Integral b => Time n -> b #ceiling :: Integral b => Time n -> b #floor :: Integral b => Time n -> b # Show n => Show (Time n) # MethodsshowsPrec :: Int -> Time n -> ShowS #show :: Time n -> String #showList :: [Time n] -> ShowS # Wrapped (Time n0) # Associated Typestype Unwrapped (Time n0) :: * # Methods_Wrapped' :: Iso' (Time n0) (Unwrapped (Time n0)) # (~) * (Time n0) t0 => Rewrapped (Time n1) t0 # type Diff Time # type Diff Time = Duration type Unwrapped (Time n0) # type Unwrapped (Time n0) = n0

toTime :: n -> Time n #

A convenient wrapper function to convert a numeric value into a time.

fromTime :: Time n -> n #

A convenient unwrapper function to turn a time into a numeric value.

data Duration n #

An abstract type representing elapsed time between two points in time. Note that durations can be negative. Literal numeric values may be used as Durations thanks to the Num and Fractional instances.

Instances

 # Methodsfmap :: (a -> b) -> Duration a -> Duration b #(<$) :: a -> Duration b -> Duration a # # Methodspure :: a -> Duration a #(<*>) :: Duration (a -> b) -> Duration a -> Duration b #(*>) :: Duration a -> Duration b -> Duration b #(<*) :: Duration a -> Duration b -> Duration a # # Methodszero :: Num a => Duration a #(^+^) :: Num a => Duration a -> Duration a -> Duration a #(^-^) :: Num a => Duration a -> Duration a -> Duration a #lerp :: Num a => a -> Duration a -> Duration a -> Duration a #liftU2 :: (a -> a -> a) -> Duration a -> Duration a -> Duration a #liftI2 :: (a -> b -> c) -> Duration a -> Duration b -> Duration c # Enum n => Enum (Duration n) # Methodssucc :: Duration n -> Duration n #pred :: Duration n -> Duration n #toEnum :: Int -> Duration n #fromEnum :: Duration n -> Int #enumFrom :: Duration n -> [Duration n] #enumFromThen :: Duration n -> Duration n -> [Duration n] #enumFromTo :: Duration n -> Duration n -> [Duration n] #enumFromThenTo :: Duration n -> Duration n -> Duration n -> [Duration n] # Eq n => Eq (Duration n) # Methods(==) :: Duration n -> Duration n -> Bool #(/=) :: Duration n -> Duration n -> Bool # Fractional n => Fractional (Duration n) # Methods(/) :: Duration n -> Duration n -> Duration n #recip :: Duration n -> Duration n # Num n => Num (Duration n) # Methods(+) :: Duration n -> Duration n -> Duration n #(-) :: Duration n -> Duration n -> Duration n #(*) :: Duration n -> Duration n -> Duration n #negate :: Duration n -> Duration n #abs :: Duration n -> Duration n #signum :: Duration n -> Duration n # Ord n => Ord (Duration n) # Methodscompare :: Duration n -> Duration n -> Ordering #(<) :: Duration n -> Duration n -> Bool #(<=) :: Duration n -> Duration n -> Bool #(>) :: Duration n -> Duration n -> Bool #(>=) :: Duration n -> Duration n -> Bool #max :: Duration n -> Duration n -> Duration n #min :: Duration n -> Duration n -> Duration n # Read n => Read (Duration n) # MethodsreadsPrec :: Int -> ReadS (Duration n) # Real n => Real (Duration n) # Methods RealFrac n => RealFrac (Duration n) # MethodsproperFraction :: Integral b => Duration n -> (b, Duration n) #truncate :: Integral b => Duration n -> b #round :: Integral b => Duration n -> b #ceiling :: Integral b => Duration n -> b #floor :: Integral b => Duration n -> b # Show n => Show (Duration n) # MethodsshowsPrec :: Int -> Duration n -> ShowS #show :: Duration n -> String #showList :: [Duration n] -> ShowS # Num n => Semigroup (Duration n) # Methods(<>) :: Duration n -> Duration n -> Duration n #sconcat :: NonEmpty (Duration n) -> Duration n #stimes :: Integral b => b -> Duration n -> Duration n # Num n => Monoid (Duration n) # Methodsmappend :: Duration n -> Duration n -> Duration n #mconcat :: [Duration n] -> Duration n # Wrapped (Duration n0) # Associated Typestype Unwrapped (Duration n0) :: * # Methods_Wrapped' :: Iso' (Duration n0) (Unwrapped (Duration n0)) # (~) * (Duration n0) t0 => Rewrapped (Duration n1) t0 # type Unwrapped (Duration n0) # type Unwrapped (Duration n0) = n0 toDuration :: n -> Duration n # A convenient wrapper function to convert a numeric value into a duration. fromDuration :: Duration n -> n # A convenient unwrapper function to turn a duration into a numeric value. ## Eras data Era n # An Era is a concrete span of time, that is, a pair of times representing the start and end of the era. Eras form a semigroup: the combination of two Eras is the smallest Era which contains both. They do not form a Monoid, since there is no Era which acts as the identity with respect to this combining operation. Era is abstract. To construct Era values, use mkEra; to deconstruct, use start and end. Instances  Show n => Show (Era n) # MethodsshowsPrec :: Int -> Era n -> ShowS #show :: Era n -> String #showList :: [Era n] -> ShowS # Ord n => Semigroup (Era n) # Methods(<>) :: Era n -> Era n -> Era n #sconcat :: NonEmpty (Era n) -> Era n #stimes :: Integral b => b -> Era n -> Era n # mkEra :: Time n -> Time n -> Era n # Create an Era by specifying start and end Times. start :: Era n -> Time n # Get the start Time of an Era. end :: Era n -> Time n # Get the end Time of an Era. duration :: Num n => Era n -> Duration n # Compute the Duration of an Era. # Dynamic values data Dynamic a # A Dynamic a can be thought of as an a value that changes over the course of a particular Era. It's envisioned that Dynamic will be mostly an internal implementation detail and that Active will be most commonly used. But you never know what uses people might find for things. Constructors  Dynamic Fieldsera :: Era Rational runDynamic :: Time Rational -> a Instances  # Methodsfmap :: (a -> b) -> Dynamic a -> Dynamic b #(<$) :: a -> Dynamic b -> Dynamic a # # Dynamic is an instance of Apply (i.e. Applicative without pure): a time-varying function is applied to a time-varying value pointwise; the era of the result is the combination of the function and value eras. Note, however, that Dynamic is not an instance of Applicative since there is no way to implement pure: the era would have to be empty, but there is no such thing as an empty era (that is, Era is not an instance of Monoid). Methods(<.>) :: Dynamic (a -> b) -> Dynamic a -> Dynamic b #(.>) :: Dynamic a -> Dynamic b -> Dynamic b #(<.) :: Dynamic a -> Dynamic b -> Dynamic a # Semigroup a => Semigroup (Dynamic a) # Dynamic a is a Semigroup whenever a is: the eras are combined according to their semigroup structure, and the values of type a are combined pointwise. Note that Dynamic a cannot be an instance of Monoid since Era is not. Methods(<>) :: Dynamic a -> Dynamic a -> Dynamic a #sconcat :: NonEmpty (Dynamic a) -> Dynamic a #stimes :: Integral b => b -> Dynamic a -> Dynamic a #

mkDynamic :: Time Rational -> Time Rational -> (Time Rational -> a) -> Dynamic a #

Create a Dynamic from a start time, an end time, and a time-varying value.

onDynamic :: (Time Rational -> Time Rational -> (Time Rational -> a) -> b) -> Dynamic a -> b #

Fold for Dynamic.

Shift a Dynamic value by a certain duration.

# Active values

For working with time-varying values, it is convenient to have an Applicative instance: <*> lets us apply time-varying functions to time-varying values; pure allows treating constants as time-varying values which do not vary. However, as explained in its documentation, Dynamic cannot be made an instance of Applicative since there is no way to implement pure. The problem is that all Dynamic values must have a finite start and end time. The solution is to adjoin a special constructor for pure/constant values with no start or end time, giving us Active.

data Active a #

There are two types of Active values:

• An Active can simply be a Dynamic, that is, a time-varying value with start and end times.
• An Active value can also be a constant: a single value, constant across time, with no start and end times.

The addition of constant values enable Monoid and Applicative instances for Active.

Instances

 # Methodsfmap :: (a -> b) -> Active a -> Active b #(<$) :: a -> Active b -> Active a # # Methodspure :: a -> Active a #(<*>) :: Active (a -> b) -> Active a -> Active b #(*>) :: Active a -> Active b -> Active b #(<*) :: Active a -> Active b -> Active a # # Methods(<.>) :: Active (a -> b) -> Active a -> Active b #(.>) :: Active a -> Active b -> Active b #(<.) :: Active a -> Active b -> Active a # Semigroup a => Semigroup (Active a) # Active values over a type with a Semigroup instance are also an instance of Semigroup. Two active values are combined pointwise; the resulting value is constant iff both inputs are. Methods(<>) :: Active a -> Active a -> Active a #sconcat :: NonEmpty (Active a) -> Active a #stimes :: Integral b => b -> Active a -> Active a # (Monoid a, Semigroup a) => Monoid (Active a) # Methodsmappend :: Active a -> Active a -> Active a #mconcat :: [Active a] -> Active a # Wrapped (Active a0) # Associated Typestype Unwrapped (Active a0) :: * # Methods_Wrapped' :: Iso' (Active a0) (Unwrapped (Active a0)) # (~) * (Active a0) t0 => Rewrapped (Active a1) t0 # type Unwrapped (Active a0) # type Unwrapped (Active a0) = MaybeApply Dynamic a0 mkActive :: Time Rational -> Time Rational -> (Time Rational -> a) -> Active a # Create a dynamic Active from a start time, an end time, and a time-varying value. fromDynamic :: Dynamic a -> Active a # Create an Active value from a Dynamic. isConstant :: Active a -> Bool # Test whether an Active value is constant. isDynamic :: Active a -> Bool # Test whether an Active value is Dynamic. onActive :: (a -> b) -> (Dynamic a -> b) -> Active a -> b # Fold for Actives. Process an 'Active a', given a function to apply if it is a pure (constant) value, and a function to apply if it is a Dynamic. modActive :: (a -> b) -> (Dynamic a -> Dynamic b) -> Active a -> Active b # Modify an Active value using a case analysis to see whether it is constant or dynamic. runActive :: Active a -> Time Rational -> a # Interpret an Active value as a function from time. activeEra :: Active a -> Maybe (Era Rational) # Get the Era of an Active value (or Nothing if it is a constant/pure value). setEra :: Era Rational -> Active a -> Active a # Set the era of an Active value. Note that this will change a constant Active into a dynamic one which happens to have the same value at all times. atTime :: Time Rational -> Active a -> Active a # atTime t a is an active value with the same behavior as a, shifted so that it starts at time t. If a is constant it is returned unchanged. activeStart :: Active a -> a # Get the value of an Active a at the beginning of its era. activeEnd :: Active a -> a # Get the value of an Active a at the end of its era. # Combinators ## Special active values ui :: Fractional a => Active a # ui represents the unit interval, which takes on the value t at time t, and has as its era [0,1]. It is equivalent to interval 0 1, and can be visualized as follows: On the x-axis is time, and the value that ui takes on is on the y-axis. The shaded portion represents the era. Note that the value of ui (as with any active) is still defined outside its era, and this can make a difference when it is combined with other active values with different eras. Applying a function with fmap affects all values, both inside and outside the era. To manipulate values outside the era specifically, see clamp and trim. To alter the values that ui takes on without altering its era, use its Functor and Applicative instances. For example, (*2) <$> ui varies from 0 to 2 over the era [0,1]. To alter the era, you can use stretch or shift.

interval a b is an active value starting at time a, ending at time b, and taking the value t at time t.

## Transforming active values

stretch :: Rational -> Active a -> Active a #

stretch s act "stretches" the active act so that it takes s times as long (retaining the same start time).

stretchTo d stretches an Active so it has duration d. Has no effect if (1) d is non-positive, or (2) the Active value is constant, or (3) the Active value has zero duration. [AJG: conditions (1) and (3) no longer true: to consider changing]

during :: Active a -> Active a -> Active a #

a1 during a2 stretches and shifts a1 so that it has the same era as a2. Has no effect if either of a1 or a2 are constant.

shift :: Duration Rational -> Active a -> Active a #

shift d act shifts the start time of act by duration d. Has no effect on constant values.

backwards :: Active a -> Active a #

Reverse an active value so the start of its era gets mapped to the end and vice versa. For example, backwards ui can be visualized as

snapshot :: Time Rational -> Active a -> Active a #

Take a "snapshot" of an active value at a particular time, resulting in a constant value.

## Working with values outside the era

clamp :: Active a -> Active a #

"Clamp" an active value so that it is constant before and after its era. Before the era, clamp a takes on the value of a at the start of the era. Likewise, after the era, clamp a takes on the value of a at the end of the era. clamp has no effect on constant values.

For example, clamp ui can be visualized as

See also clampBefore and clampAfter, which clamp only before or after the era, respectively.

clampBefore :: Active a -> Active a #

"Clamp" an active value so that it is constant before the start of its era. For example, clampBefore ui can be visualized as

See the documentation of clamp for more information.

clampAfter :: Active a -> Active a #

"Clamp" an active value so that it is constant after the end of its era. For example, clampBefore ui can be visualized as

See the documentation of clamp for more information.

trim :: Monoid a => Active a -> Active a #

"Trim" an active value so that it is empty outside its era. trim has no effect on constant values.

For example, trim ui can be visualized as

Actually, trim ui is not well-typed, since it is not guaranteed that ui's values will be monoidal (and usually they won't be)! But the above image still provides a good intuitive idea of what trim is doing. To make this precise we could consider something like trim (First . Just \$ ui).

See also trimBefore and trimActive, which trim only before or after the era, respectively.

trimBefore :: Monoid a => Active a -> Active a #

"Trim" an active value so that it is empty before the start of its era. For example, trimBefore ui can be visualized as

See the documentation of trim for more details.

trimAfter :: Monoid a => Active a -> Active a #

"Trim" an active value so that it is empty after the end of its era. For example, trimAfter ui can be visualized as

See the documentation of trim for more details.

## Composing active values

after :: Active a -> Active a -> Active a #

a1 after a2 produces an active that behaves like a1 but is shifted to start at the end time of a2. If either a1 or a2 are constant, a1 is returned unchanged.

(->>) :: Semigroup a => Active a -> Active a -> Active a infixr 5 #

Sequence/overlay two Active values: shift the second to start immediately after the first (using after), then compose them (using <>).

(|>>) :: Active a -> Active a -> Active a #

"Splice" two Active values together: shift the second to start immediately after the first (using after), and produce the value which acts like the first up to the common end/start point, then like the second after that. If both are constant, return the first.

movie :: [Active a] -> Active a #

Splice together a list of active values using |>>. The list must be nonempty.

# Discretization

discrete :: [a] -> Active a #

Create an Active which takes on each value in the given list in turn during the time [0,1], with each value getting an equal amount of time. In other words, discrete creates a "slide show" that starts at time 0 and ends at time 1. The first element is used prior to time 0, and the last element is used after time 1.

It is an error to call discrete on the empty list.

simulate :: Rational -> Active a -> [a] #

simulate r act simulates the Active value act, returning a list of "snapshots" taken at regular intervals from the start time to the end time. The interval used is determined by the rate r, which denotes the "frame rate", that is, the number of snapshots per unit time.

If the Active value is constant (and thus has no start or end times), a list of length 1 is returned, containing the constant value.