Timers in Elm

import Time
import Task exposing (Task)
import Process

-- We often want to execute Elm code at some point in the
-- future, or repeatedly at some interval. Elm's `Time` module
-- provides functionality for these tasks. We'll look at how
-- to create delays and cancellable tasks.

main =
    -- Timers represent a single event in the future. You
    -- tell the timer how long you want to wait, and it
    -- provides a task that will be completed at that
    -- time. This timer will wait 2 seconds.
    Task.perform (\_ -> Debug.log "Timer 1 fired" ()) (Process.sleep 2000)

    -- If you just wanted to wait, you could have used
    -- `Process.sleep`. One reason a timer may be useful is
    -- that you can cancel the task before it completes.
    -- Here's an example of that.
    let
        timer2 =
            Process.sleep 1000
                |> Task.andThen (\_ -> Task.succeed (Debug.log "Timer 2 fired" ()))

        cancelTimer2 =
            Task.cancel timer2
    in
    Task.attempt
        (\result ->
            case result of
                Ok _ ->
                    Debug.log "Timer 2 completed" ()

                Err _ ->
                    Debug.log "Timer 2 cancelled" ()
        )
        cancelTimer2

    -- Give the timer2 enough time to fire, if it ever
    -- was going to, to show it is in fact cancelled.
    Process.sleep 2000
        |> Task.perform (\_ -> ())

To run this Elm program, you would typically compile it to JavaScript and run it in a browser or with Node.js. Here’s an example of how you might see the output:

> elm make Main.elm --output=main.js
> node main.js
Timer 1 fired
Timer 2 cancelled

The first timer will fire ~2s after we start the program, but the second should be cancelled before it has a chance to fire.

In Elm, we use the Time module and Process.sleep to create delays. Instead of channels, we use Tasks, which can be performed or attempted. The Task.cancel function allows us to cancel a task before it completes.

Note that Elm is a purely functional language, so side effects like logging are handled through the Debug.log function in this example. In a real application, you’d typically use the Elm Architecture to handle effects and update your view.

Elm doesn’t have direct equivalents to Go’s goroutines, but it provides its own mechanisms for handling asynchronous operations in a functional and type-safe manner.