packages feed

elm-core-sources-1.0.0: src/Signal.elm

module Signal where
{-| The library for general signal manipulation. Includes mapping, merging,
filters, past-dependence, and helpers for handling inputs from the UI.

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`](Time) library.

# Merging
@docs merge, mergeMany

# Mapping
@docs map, map2, map3, map4, map5

# Fancy Mapping
@docs (<~), (~)

# Past-Dependence
@docs foldp

# Filters
@docs keepIf, dropIf, keepWhen, dropWhen, dropRepeats, sampleOn

# Channels
@docs channel, send, subscribe

# Constants
@docs constant

-}

import Native.Signal
import List
import Basics (fst, snd, not)

type Signal a = Signal

{-| Create a constant signal that never changes. -}
constant : a -> Signal a
constant = Native.Signal.constant

{-| Apply a function to the current value of a signal.

    mouseIsUp : Signal Bool
    mouseIsUp =
        map not Mouse.isDown

    main : Signal Element
    main =
        map toElement Mouse.position
-}
map : (a -> result) -> Signal a -> Signal result
map = Native.Signal.map

{-| Apply a function to the current value of two signals. In the following
example, we figure out the `aspectRatio` of the window by combining the
current width and height.

    ratio : Int -> Int -> Float
    ratio width height =
        toFloat width / toFloat height

    aspectRatio : Signal Float
    aspectRatio =
        map2 ratio Window.width Window.height
-}
map2 : (a -> b -> result) -> Signal a -> Signal b -> Signal result
map2 = Native.Signal.map2

map3 : (a -> b -> c -> result) -> Signal a -> Signal b -> Signal c -> Signal result
map3 = Native.Signal.map3

map4 : (a -> b -> c -> d -> result) -> Signal a -> Signal b -> Signal c -> Signal d -> Signal result
map4 = Native.Signal.map4

map5 : (a -> b -> c -> d -> e -> result) -> Signal a -> Signal b -> Signal c -> Signal d -> Signal e -> Signal result
map5 = Native.Signal.map5


{-| Create a past-dependent signal. Each update from the incoming signals will
be used to step the stat forward. The outgoing signal represents the current
state.

    clickCount : Signal Int
    clickCount =
        foldp (\click total -> total + 1) 0 Mouse.clicks

    timeSoFar : Signal Time
    timeSoFar =
        foldp (+) 0 (fps 40)

So `clickCount` updates on each mouse click, incrementing by one. `timeSoFar`
is the time the program has been running, updated 40 times a second.
-}
foldp : (a -> state -> state) -> state -> Signal a -> Signal state
foldp = Native.Signal.foldp


{-| Merge two signals into one. This function is extremely useful for bringing
together lots of different signals to feed into a `foldp`.

    type Update = MouseMove (Int,Int) | TimeDelta Float

    updates : Signal Update
    updates =
        merge
            (map MouseMove Mouse.position)
            (map TimeDelta (fps 40))

If an update comes from either of the incoming signals, it updates the outgoing
signal. If an update comes on both signals at the same time, the left update
wins.
-}
merge : Signal a -> Signal a -> Signal a
merge = Native.Signal.merge

{-| Merge many signals into one. This is useful when you are merging more than
two signals. When multiple updates come in at the same time, the left-most
update wins, just like with `merge`.

    type Update = MouseMove (Int,Int) | TimeDelta Float | Click

    updates : Signal Update
    updates =
        mergeMany
            [ map MouseMove Mouse.position
            , map TimeDelta (fps 40)
            , map (always Click) Mouse.clicks
            ]
-}
mergeMany : List (Signal a) -> Signal a
mergeMany signals =
    List.foldr1 merge signals


{-| Filter out some updates. The given function decides whether we should
keep an update. If no updates ever flow through, we use the default value
provided. The following example only keeps even numbers and has an initial
value of zero.

    numbers : Signal Int

    isEven : Int -> Bool

    evens : Signal Int
    evens =
        keepIf isEven 0 numbers
-}
keepIf : (a -> Bool) -> a -> Signal a -> Signal a
keepIf =
    Native.Signal.keepIf


{-| Filter out some updates. The given function decides whether we should
drop an update. If we drop all updates, we use the default value provided.
The following example drops all even numbers and has an initial value of
one.

    numbers : Signal Int

    isEven : Int -> Bool

    odds : Signal Int
    odds =
        dropIf isEven 1 numbers
-}
dropIf : (a -> Bool) -> a -> Signal a -> Signal a
dropIf =
    Native.Signal.dropIf


{-| Keep updates when the first signal is true. You provide a default value
just in case that signal is *never* true and no updates make it through. For
example, here is how you would capture mouse drags.

    dragPosition : Signal (Int,Int)
    dragPosition =
        keepWhen Mouse.isDown (0,0) Mouse.position
-}
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. You provide a default value
just in case that signal is *always* true and we drop all updates.
-}
dropWhen : Signal Bool -> a -> Signal a -> Signal a
dropWhen bs = keepWhen (not <~ bs)


{-| Drop updates that repeat the current value of the signal.

    numbers : Signal Int

    noDups : Signal Int
    noDups =
        dropRepeats numbers

    --  numbers => 0 0 3 3 5 5 5 4 ...
    --  noDups  => 0   3   5     4 ...
-}
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 `map`. A prettier way to apply a function to the current value
of a signal.
-}
(<~) : (a -> b) -> Signal a -> Signal b
f <~ s = Native.Signal.map f s


{-| Intended to be paired with the `(<~)` operator, this makes it possible for
many signals to flow into a function. Think of it as a fancy alias for `mapN`.
For example, the following declarations are equivalent:

    main : Signal Element
    main =
      scene <~ Window.dimensions ~ Mouse.position

    main : Signal ELement
    main =
      map2 scene Window.dimensions Mouse.position

You can use this pattern for as many signals as you want by using `(~)` a bunch
of times, so you can go higher than `map5` if you need to.
-}
(~) : Signal (a -> b) -> Signal a -> Signal b
sf ~ s = Native.Signal.map2 (\f x -> f x) sf s

infixl 4 <~
infixl 4 ~


---- INPUTS ----

type Channel a = Channel -- Signal a

type Message = Message -- () -> ()


{-| Create a signal channel that you can `send` messages to. To receive these
messages, `subscribe` to the channel and turn it into a normal signal. The
primary use case is receiving updates from UI elements such as buttons and
text fields. The argument is a default value for the custom signal.

Note: This is an inherently impure function, so `(channel ())`
and `(channel ())` produce two different signals.
-}
channel : a -> Channel a
channel =
    Native.Signal.input


{-| Create a `Message` that can be sent to an `Channel` with a handler like
`Html.onclick` or `Html.onblur`.

    import Html

    type Update = NoOp | Add Int | Remove Int

    updates : Channel Update
    updates = channel NoOp

    addButton : Html.Html
    addButton =
        Html.button
            [ onclick (send updates (Add 1)) ]
            [ Html.text "Add 1" ]
-}
send : Channel a -> a -> Message
send =
    Native.Signal.send


{-| Receive all the messages sent to an `Channel` as a `Signal`. The following
example shows how you would set up a system that uses an `Channel`.

    -- initialState : Model
    -- type Update = NoOp | ...
    -- step : Update -> Model -> Model
    -- view : Channel Update -> Model -> Element

    updates : Channel Update
    updates = channel NoOp

    main : Signal Element
    main =
      map
        (view updates)
        (foldp step initialState (subscribe updates))

The `updates` channel appears twice in `main` because it serves as a bridge
between your view and your signals. In the view you `send` to it, and in signal
world you `subscribe` to it.
-}
subscribe : Channel a -> Signal a
subscribe =
    Native.Signal.subscribe