elm/libraries/Signal.elm
2014-03-23 17:21:44 -07:00

175 lines
6.3 KiB
Elm

module Signal where
{-| The library for general signal manipulation. Includes lift functions up to
`lift8` and infix lift operators `<~` and `~`, combinations, filters, and
past-dependence.
Signals are time-varying values. Lifted functions are reevaluated whenever any of
their input signals has an event. Signal events may be of the same value as the
previous value of the signal. Such signals are useful for timing and
past-dependence.
Some useful functions for working with time (e.g. setting FPS) and combining
signals and time (e.g. delaying updates, getting timestamps) can be found in
the `Time` library.
# Combine
@docs constant, lift, lift2, merge, merges, combine
# Past-Dependence
@docs foldp, count, countIf
#Filters
@docs keepIf, dropIf, keepWhen, dropWhen, dropRepeats, sampleOn
# Pretty Lift
@docs (<~), (~)
# Do you even lift?
@docs lift3, lift4, lift5, lift6, lift7, lift8
-}
import Native.Signal
import List (foldr)
import Basics (fst, snd, not)
import Native.Error
import Maybe as M
data Signal a = Signal
{-| Create a constant signal that never changes. -}
constant : a -> Signal a
constant = Native.Signal.constant
{-| Transform a signal with a given function. -}
lift : (a -> b) -> Signal a -> Signal b
lift = Native.Signal.lift
{-| Combine two signals with a given function. -}
lift2 : (a -> b -> c) -> Signal a -> Signal b -> Signal c
lift2 = Native.Signal.lift2
lift3 : (a -> b -> c -> d) -> Signal a -> Signal b -> Signal c -> Signal d
lift3 = Native.Signal.lift3
lift4 : (a -> b -> c -> d -> e) -> Signal a -> Signal b -> Signal c -> Signal d -> Signal e
lift4 = Native.Signal.lift4
lift5 : (a -> b -> c -> d -> e -> f) -> Signal a -> Signal b -> Signal c -> Signal d -> Signal e -> Signal f
lift5 = Native.Signal.lift5
lift6 : (a -> b -> c -> d -> e -> f -> g)
-> Signal a -> Signal b -> Signal c -> Signal d -> Signal e -> Signal f -> Signal g
lift6 = Native.Signal.lift6
lift7 : (a -> b -> c -> d -> e -> f -> g -> h)
-> Signal a -> Signal b -> Signal c -> Signal d -> Signal e -> Signal f -> Signal g -> Signal h
lift7 = Native.Signal.lift7
lift8 : (a -> b -> c -> d -> e -> f -> g -> h -> i)
-> Signal a -> Signal b -> Signal c -> Signal d -> Signal e -> Signal f -> Signal g -> Signal h -> Signal i
lift8 = Native.Signal.lift8
{-| Create a past-dependent signal. Each value given on the input signal will
be accumulated, producing a new output value.
For instance, `foldp (+) 0 (fps 40)` is the time the program has been running,
updated 40 times a second. -}
foldp : (a -> b -> b) -> b -> Signal a -> Signal b
foldp = Native.Signal.foldp
{-| Merge two signals into one, biased towards the first signal if both signals
update at the same time. -}
merge : Signal a -> Signal a -> Signal a
merge = Native.Signal.merge
{-| Merge many signals into one, biased towards the left-most signal if multiple
signals update simultaneously. -}
merges : [Signal a] -> Signal a
merges = Native.Signal.merges
{-| Combine a list of signals into a signal of lists. -}
combine : [Signal a] -> Signal [a]
combine = foldr (Native.Signal.lift2 (::)) (Native.Signal.constant [])
-- Merge two signals into one, but distinguishing the values by marking the first
-- signal as `Left` and the second signal as `Right`. This allows you to easily
-- fold over non-homogeneous inputs.
-- mergeEither : Signal a -> Signal b -> Signal (Either a b)
{-| Count the number of events that have occurred. -}
count : Signal a -> Signal Int
count = Native.Signal.count
{-| Count the number of events that have occurred that satisfy a given predicate.
-}
countIf : (a -> Bool) -> Signal a -> Signal Int
countIf = Native.Signal.countIf
{-| Keep only events that satisfy the given predicate. Elm does not allow
undefined signals, so a base case must be provided in case the predicate is
not satisfied initially. -}
keepIf : (a -> Bool) -> a -> Signal a -> Signal a
keepIf = Native.Signal.keepIf
{-| Drop events that satisfy the given predicate. Elm does not allow undefined
signals, so a base case must be provided in case the predicate is satisfied
initially. -}
dropIf : (a -> Bool) -> a -> Signal a -> Signal a
dropIf = Native.Signal.dropIf
{-| Keep events only when the first signal is true. When the first signal
becomes true, the most recent value of the second signal will be propagated.
Until the first signal becomes false again, all events will be propagated. Elm
does not allow undefined signals, so a base case must be provided in case the
first signal is not true initially. -}
keepWhen : Signal Bool -> a -> Signal a -> Signal a
keepWhen bs def sig =
snd <~ (keepIf fst (False, def) ((,) <~ (sampleOn sig bs) ~ sig))
{-| Drop events when the first signal is true. When the first signal becomes
false, the most recent value of the second signal will be propagated. Until the
first signal becomes true again, all events will be propagated. Elm does not
allow undefined signals, s oa base case must be provided in case the first
signal is true initially. -}
dropWhen : Signal Bool -> a -> Signal a -> Signal a
dropWhen bs = keepWhen (not <~ bs)
{-| Drop updates that repeat the current value of the signal.
Imagine a signal `numbers` has initial value
0 and then updates with values 0, 0, 1, 1, and 2. `dropRepeats numbers`
is a signal that has initial value 0 and updates as follows: ignore 0,
ignore 0, update to 1, ignore 1, update to 2. -}
dropRepeats : Signal a -> Signal a
dropRepeats = Native.Signal.dropRepeats
{-| Sample from the second input every time an event occurs on the first input.
For example, `(sampleOn clicks (every second))` will give the approximate time
of the latest click. -}
sampleOn : Signal a -> Signal b -> Signal b
sampleOn = Native.Signal.sampleOn
{-| An alias for `lift`. A prettier way to apply a function to the current value
of a signal. -}
(<~) : (a -> b) -> Signal a -> Signal b
f <~ s = Native.Signal.lift f s
{-| Informally, an alias for `liftN`. Intersperse it between additional signal
arguments of the lifted function.
Formally, signal application. This takes two signals, holding a function and
a value. It applies the current function to the current value.
The following expressions are equivalent:
scene <~ Window.dimensions ~ Mouse.position
lift2 scene Window.dimensions Mouse.position
-}
(~) : Signal (a -> b) -> Signal a -> Signal b
sf ~ s = Native.Signal.lift2 (\f x -> f x) sf s
infixl 4 <~
infixl 4 ~