Migrating signal usage from Elm 0.16 to 0.17.
Moving signal transformations away from signals.
In Elm 0.16, in order to manipulate and transform inputs from signals, we had four main functions at hand:
map
, for transformationsfilter
, for filteringmerge
, for joiningfoldp
, for stateful transformations over time
The following examples will show how this has changed, and how to do it in the new world. For the most uses, we can easily replace it with the new syntax.
map
map
would allow you to take a signal of one kind, and turn it to a signal of another kind. This looks like this:
0.16
type alias Model =
{ currentTime : Time
}
type Action
= SetCurrentTime Time
| NoOp
currentTime : Signal Time
currentTime = Time.every Time.second
setCurrentTime : Signal
setCurrentTime = Signal.map SetCurrentTime currentTime
update : Action -> Model -> (Model, Effects Action)
update action model =
case action of
NoOp ->
( model, Effects.none)
SetCurrentTime time ->
( { model | currentTime = time }
, Effects.none
)
init : Model
init =
{ currentTime = 0
}
view : Signal.Address Action -> Model -> Html
view address model =
div
[]
[ text <| toString <| model.currentTime]
app : StartApp Model Action
app =
StartApp.start
{ init = (init, Effects.none)
, view = view
, update = update
, inputs = [ setCurrentTime ]
}
main : Signal Html
main = app.html
0.17
In 0.17, this now looks like this:
type alias Model =
{ currentTime : Time
}
type Msg
= SetCurrentTime Time
| NoOp
-- Every now takes a "tagger" function.
-- Each time a second goes by, it will wrap the time with SetCurrentTime
-- and then send this message to the update loop
setCurrentTime : Sub Msg
setCurrentTime = Time.every Time.second SetCurrentTime
update : Msg -> Model -> (Model, Cmd Msg)
update action model =
case action of
NoOp ->
( model, Cmd.none)
SetCurrentTime time ->
( { model | currentTime = time }
, Cmd.none
)
init : Model
init =
{ currentTime = 0
}
view : Model -> Html Action
view model =
div
[]
[ text <| toString <| model.currentTime]
-- Here, we use the model to figure out what we are currently subscribed to
-- We will use this more in the future
handleSubs : Model -> Sub Action
handleSubs model =
setCurrentTime
main : Program Never
main =
Html.program
{ init = (init, Cmd.none)
, view = view
, update = update
, subscriptions = handleSubs
}
Filter
Filters allow you to only listen to values when they pass some evaluation. They're useful for controlling when things get sent to your update loop. For example, if we don't want to update the current time, based on whether or not something is even, then we might do something like this:
0.16
-- only produce a changed value if the time is even
currentTime : Signal Time
currentTime =
Time.every Time.second
|> Signal.filter (\x -> x % 2 == 0)
setCurrentTime : Signal Action
setCurrentTime = Signal.map SetCurrentTime currentTime
0.17
In 0.17, we can that too - but it looks like this instead.
type Msg
= SetCurrentTime Time
| NoOp
setCurrentTime : Sub Msg
setCurrentTime =
Time.every Time.second
|> (\time ->
if time % 2 == 0 then
SetCurrentTime time
else
NoOp
)
0.16
What about if we wanted to get the current time, only if a certain boolean was enabled?
currentTime : Signal Time
currentTime =
Time.every Time.second
|> Signal.filter (\x -> x % 2 == 0)
setCurrentTime : Signal Action
setCurrentTime =
Signal.map2 (\time model -> if model.isListening then SetCurrentTime Time else NoOp) currentTime app.model)
0.17
In 0.17, this logic is handled in our handleSubs
function. This is the reason why we pass in the model
to the subscriptions function - it's important to be able to change what we are subscribed to.
handleSubs : Model -> Sub Action
handleSubs model =
if model.isListening then
Time.every Time.second SetCurrentTime
else
Sub.none
The cool thing about subscriptions in 0.17 is that if we aren't currently subscribed to a subscription, that given subscription will stop being listened to entirely. This was not the case in 0.16
Merge
Merge allows you to take two signals of the same type, and create a new signal that will produce values when either of the signals merged changes. In the case of a collision, the left signal would win.
0.16
In 0.16, it would look something like this:
setMouseMove : Signal Action
setMouseMove = Signal.map SetMouseMove Mouse.moves
allSignals : Signal Action
allSignals =
Signal.merge setCurrentTime setMouseMove
0.17
In 0.17, this is pretty much the same. Instead of merge
, we use batch
. This is similiar to mergeMany
in 0.16, or the concept of Effects.batch
.
handleSubs : Model -> Sub Action
handleSubs model =
Sub.batch
[ Time.every Time.second SetCurrentTime
, Mouse.moves SetMouseMove
]
Foldp
Foldp is complicated. If you were using foldp before, then you probably want to be using just an update loop now. If you had a legitimate use cases for using foldp separately from your main model or update function, then let us know on #elm-dev Slack channel or the elm-dev mailing list.