Create a new Elm program, Stopwatch.elm
.
Note: If you find typing all this out tedious, most Elm language support packages for popular editors include a snippet for a new program. In Visual Studio Code, for example, with the Elm extension installed, you can type
Html.program
in an empty file, hit Tab, and have all of this code generated for you automatically.
module Stopwatch exposing (..)
import Html exposing (Html, div, button, text)
main : Program Never Model Msg
main =
Html.program
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}
type Msg
= Todo
type alias Model =
{}
init : ( Model, Cmd Msg )
init =
( {}, Cmd.none )
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
( model, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
All that’s missing from this program
is a view
function
(the Elm compiler will point this out helpfully
if you try to run it).
Although our ultimate aim is to
create a fully-functional stopwatch,
we’ll start simple.
Let’s make this program
display the current time
when it starts.
Since Elm requires the view
function to render our program
using nothing but the current model,
let’s assume our model will contain the current time,
which we’ll convert to a string
with toString
:
view : Model -> Html Msg
view model =
text (toString model.time)
Compile the program now
(if you’re using elm-reactor, just refresh your browser).
The compiler will complain
that our model doesn’t have a field named time
.
So, it’s clear what we need to do next:
add that field to our Model
type.
type alias Model =
{ time : Time }
In order to use the Time
type,
we’ll need to add an import
for it
to the top of this module:
import Time exposing (Time)
This process of pretending that something exists
(in this example, model.time
in our view
)
and then following the compiler’s directions to add it
is a really nice way to add features to an Elm program.
Try to think of every compiler error as
helpful guidance about what to do next,
not a complaint that you’re doing something wrong.
With these changes in place,
the compiler will have a new direction for us:
the model returned by our init
function
is missing the time
field.
Let’s fix it!
init : ( Model, Cmd Msg )
init =
( { time = Time.now }, Cmd.none )
When our program starts up,
we want the init
function to
set the time
in our model to the current time.
Looking at the Elm Core API reference,
we can see that the Time
module has a now
function,
so it seems natural to use that here.
But the Elm compiler points out
that Time.now
doesn’t return a Time
;
it returns a Task.Task x Time
.
What is a Task.Task x Time
?
Remember that as a pure functional language,
Elm doesn’t have functions that
return different values at different times
as we might expect Time.now
to do.
Instead, Time.now
returns a task that
we can command the Elm runtime to perform.
When it completes this task,
the Elm runtime will send our update
function a message
with the current time.
We’ll see how to perform tasks like this shortly,
but right now
we need to get our init
function to compile.
If we’re going to command Elm to obtain the current time,
that means our program’s initial model
must represent the state where
we are still waiting to receive that time.