A signal is a value that changes over time. For example, we can think of the mouse position as a pair of numbers that is changing over time, whenever the user moves the mouse.
Mouse.position : Signal (Int,Int)Another signal is the Element or Html we want to show on screen.
main : Signal HtmlAs the Html changes, the user sees different things on screen automatically.
Some useful functions for working with time (e.g. setting FPS) and combining
signals and time (e.g. timestamps) can be found in the Time library.
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 update provided by the left input signal wins (i.e., the update from the second signal is discarded).
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
]Apply a function to a signal.
mouseIsUp : Signal Bool
mouseIsUp =
map not Mouse.isDown
main : Signal Element
main =
map Graphics.Element.show Mouse.positionApply a function to the current value of two signals. The function
is reevaluated whenever either signal changes. 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.heightCreate a past-dependent signal. Each update from the incoming signals will be used to step the state 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.
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 =
filter isEven 0 numbersFilter out some updates. When the filter function gives back Just a
value, we send that value along. When it returns Nothing we drop it.
If the initial value of the incoming signal turns into Nothing, we use the
default value provided. The following example keeps only strings that can be
read as integers.
userInput : Signal String
toInt : String -> Maybe Int
numbers : Signal Int
numbers =
filterMap toInt 0 userInputDrop 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 ...The signal should not be a signal of functions, or a record that contains a function (you'll get a runtime error since functions cannot be equated).
Sample from the second input every time an event occurs on the first input.
For example, (sampleOn Mouse.clicks (Time.every Time.second)) will give the
approximate time of the latest click.
A Mailbox is a communication hub. It is made up of
* an Address that you can send messages to
* a Signal of messages sent to the mailbox
An Address points to a specific signal. It allows you to feed values into
signals, so you can provide your own signal updates.
The primary use case is when a Task or UI element needs to talk back to the
main part of your application.
Create a mailbox you can send messages to. The primary use case is receiving updates from tasks and UI elements. The argument is a default value for the custom signal.
Note: Creating new signals is inherently impure, so (mailbox ()) and
(mailbox ()) produce two different mailboxes.
A Message is like an envelope that you have not yet put in a mailbox.
The address is filled out and the envelope is filled, but it will be sent at
some point in the future.
Create a message that may be sent to a Mailbox at a later time.
Most importantly, this lets us create APIs that can send values to ports without allowing people to run arbitrary tasks.
Create a new address. This address will tag each message it receives and then forward it along to some other address.
type Action = Undo | Remove Int | NoOp
actions : Mailbox Action
actions = mailbox NoOp
removeAddress : Address Int
removeAddress =
forwardTo actions.address RemoveIn this case we have a general address that many people may send
messages to. The new removeAddress tags all messages with the Remove tag
before forwarding them along to the more general address. This means
some parts of our application can know only about removeAddress and not
care what other kinds of Actions are possible.
Send a message to an Address.
type Action = Undo | Remove Int
address : Address Action
requestUndo : Task x ()
requestUndo =
send address UndoThe Signal associated with address will receive the Undo message
and push it through the Elm program.
Create a signal that never changes. This can be useful if you need to pass a combination of signals and normal values to a function:
map3 view Window.dimensions Mouse.position (constant initialModel)
A value that changes over time. So a
(Signal Int))is an integer that is varying as time passes, perhaps representing the current window width of the browser. Every signal is updated at discrete moments in response to events in the world.