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 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 toElement 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.heightAn alias for map. A prettier way to apply a function to the current value
of a signal.
main : Signal Html
main =
view <~ model
model : Signal Model
view : Model -> HtmlIntended 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.positionYou 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.
Create 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.
An 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
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.
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 will tag each message it receives and then forward it along to some other address.
type Action = Undo | Remove Int
port actions : Mailbox Action
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)
Merge two signals into one. This function is extremely useful for bringing together lots of different signals to feed into a
foldp.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 (i.e., the right update is discarded).