Real World Elm
Just enough to build complex Elm applications
How a pure functional language interacts with the “world”?
Tech Lead @ Meetic


k.lebrun@meetic-corp.com
kevinlebrun

Signals
Event based / Infinite
Simple signals

Transform
position : Signal (Int, Int)
position =
Signal.map2 (,) Mouse.x Mouse.y
Transform
type Action
= NoOp
| Pause
| Resume
keypresses : Signal Action
keypresses =
let
handleKeyboard : KeyCode -> Action
handleKeyboard code =
let
key = fromCode code
in
if key == 'P' || key == 'p' then
Pause
else if key == 'R' || key == 'r' then
Resume
else
NoOp
in
Signal.map handleKeyboard Keyboard.presses
Merge
keypresses : Signal Action
keypresses = ...
points : Signal Action
points = ...
inputs : Signal Action
inputs = Signal.mergeMany
[ points
, keypresses
]
Manage state
model : Signal Model
model =
let
inputs = Signal.mergeMany
[ points
, keypresses
]
in
Signal.foldp update initialModel inputs
Filter
dropNoOp =
Signal.filter ((/=) NoOp) NoOp keypresses
Mailbox
-- create a new mailbox with a default value
actions : Signal.Mailbox Action
actions =
Signal.mailbox NoOp
-- compute a new model after we receive
-- a new message in the mailbox (via signal)
model : Signal Model
model =
Signal.foldp update canvas actions.signal
-- pass the mailbox address to the view
main : Signal Html
main =
Signal.map (view actions.address) model
-- dispatch a new action into the mailbox address
view : Signal.Address Action -> Model -> Html
view address model =
div [ class "container" ]
[ button [ onClick address Pause ] [ text "Pause" ]
, button [ onClick address Resume ] [ text "Resume" ]
]


Delegation
-- forward the view address to delegate
viewCounter : Signal.Address Action -> (ID, Counter.Model) -> Html
viewCounter address (id, model) =
Counter.view (Signal.forwardTo address (Modify id)) model
-- delegate model update
type Action = Modify ID Counter.Action
update : Action -> Model -> Model
update action model =
case action of
Modify id counterAction ->
let updateCounter (counterID, counterModel) =
if counterID == id then
(counterID, Counter.update counterAction counterModel)
else
(counterID, counterModel)
in
{ model | counters = List.map updateCounter model.counters }
Debounce
debounce : Time.Time -> Signal a -> Signal a
debounce wait signal =
Signal.sampleOn (
Time.since wait signal
|> Signal.dropRepeats
|> Signal.filter ((==) False) False
) signal
Debug
debounce : Time.Time -> Signal a -> Signal a
debounce wait signal =
Signal.sampleOn (
Time.since wait signal
|> Signal.map (Debug.log "dropped")
<< Signal.dropRepeats
|> Signal.map (Debug.log "filtered")
<< Signal.filter ((==) False) False
) signal
Ports
How Elm interacts with JavaScript?
Incoming ports
port incomingPoints : Signal Point
var div = document.getQuerySelector('.component');
var myapp = Elm.embed(Elm.Main, div);
myapp.ports.incomingPoints.send([0, 0]);
myapp.ports.incomingPoints.send([50, 50]);


Outgoing ports
port outgoingPoints : Signal Point
port outgoingPoints =
points
myapp.ports.outgoingPoints.subscribe(function (point) {
console.log(point);
});


LocalStorage
myapp.ports.savePoints.subscribe(function (point) {
localStorage.setItem(point.id, JSON.stringify(point));
});
port savePoint : Signal Point
port savePoint =
points


Ask for confirmation
port getDeleteConfirmation : Signal Int
port askDeleteConfirmation : Signal Point
port askDeleteConfirmation =
Signal.filterMap extractDeletionIntent actions.signal
app.ports.askDeleteConfirmation.subscribe(function (args) {
console.log('askDeleteConfirmation', args);
var id = args[0];
var message = args[1];
var response = window.confirm(message);
if (response) {
app.ports.getDeleteConfirmation.send(id);
}
})


Tasks
Communicate with the “world”
A simple task
fetchEmail : String -> Task Http.Error String
fetchEmail username =
Http.get decodeEmail ("https://api.github.com/users/" ++ username)
decodeEmail : Decoder String
decodeEmail =
at ["email"] string
Transform
fetchEmail : String -> Task Http.Error String
fetchEmail username =
Http.get decodeEmail ("https://api.github.com/users/" ++ username)
fetchFullName : String -> Task Http.Error String
fetchFullName username =
Task.map2 (++) (fetchFirstName username) (fetchLastName username)
fetchEmailMaybe : String -> Task x (Maybe String)
fetchEmailMaybe username =
fetchEmail username
|> Task.toMaybe
fetchEmailResult : String -> Task x (Result Http.Error String)
fetchEmailResult username =
fetchEmail username
|> Task.toResult
Chain
fetchEmailThenUpdate : Signal Task x ()
fetchEmailThenUpdate username =
fetchEmail username
`andThen` (\email -> Signal.send actions.address (OnEmailReceived email))
-- `andThen` (OnEmailReceived << Signal.send actions.address)
Execute a task
port runner : Signal (Task x ())
port runner =
fetchEmailThenUpdate query.signal
port runner : Signal (Task x ())
port runner =
let
extractQuery action =
case action of
UpdateQuery query ->
Just query
_ ->
Nothing
query = debounce 500 <| Signal.filterMap extractQuery "" actions.signal
in
Signal.map fetchEmailThenUpdate query
How a pure functional language interacts with the “world”?
How to start?
Q/A


k.lebrun@meetic-corp.com
kevinlebrun
Real World Elm
By Kevin Le Brun
Real World Elm
- 2,832