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