<VR>

  • Home
  • Talks
  • Podcast
  • Blog

Writing a simple program in Elm

Published on Sep 03, 2021

🛫3 mins to read

  • elm
  • javascript
  • ui
  • init
  • msg
  • code

After reading and listening to a LOT of literature on Elm, I wanted to try it out in a simple program. I have a section on my website for the contact form that seemed like a simple enough place to start that's neither too simple nor too complex.

Image of the component we will be attempting

Functionality

You can try this out in the contact section of this website on the homepage. It's a simple form with mostly static text. The only piece of dynamic functionality is when you click on the different reasons to contact me, I pop in some additional copy above the form and fill in the subject line in the form.

Modeling the data

I started by creating a model of the state in the application

{ subject = "" , copy = "" }

The way we use the commas here is different but having listened to all the reasoning around it, I think this is a better way and would like to structure commas even in my JS applications this way.

Adding Msg to update my model

Then I started adding all possible messages through which my model can change.

type Msg = Coffee | Meetup | OpenSource | Talk

This part felt weird coming from JS because of the values that Msg can take are straight up tokens and not strings within quotes. In TS, you could do this but you would have to define what each type meant. Eg. Coffee: String and then combine them together. I like that you can just type them but it's logic that's very specific to your app .

Flesh out the update method

update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Coffee -> ( { model | subject = "Grab coffee", copy = "I'm down to grabbing coffee talking about your startup, a new opportunity or just plain banter." }, Cmd.none ) Meetup -> ( { model | subject = "Organize a meetup", copy = "I love participating in interest-based comnunities in real life. Running a meetup can be hard and I'd love to help in anyway I can." }, Cmd.none ) OpenSource -> ( { model | subject = "Open source help", copy = "I am always looking to contribute to projects in meaningful ways. I enjoy coding and writing clear technicaly documentation." }, Cmd.none ) Talk -> ( { model | subject = "Request a talk", copy = "I'm seeking opportunities to speak at international conferences and local meetups this year. Definitely reach out if you see a good fit." }, Cmd.none )

Hooking them up together was so simple. My view is simply two divs that show whatever is currently the model copy and subject with 4 buttons to toggle between them.

view model = div [] [ button [ onClick Coffee ] [ text "Coffee" ] , button [ onClick Meetup ] [ text "Meetup" ] , button [ onClick OpenSource ] [ text "Open Source" ] , button [ onClick Talk ] [ text "Talk" ] , div [] [ text model.subject ] , div [] [ text model.copy ] ]

This was so simple to hookup and very little boilerplate. Of course, writing the view in Elm felt a little different compared to writing in JSX which feels very natural and HTML-like. But here's the output. Right now, this works as a simple UI when you click a button, it triggers a change in some copy

Empty screen on initial loadon clicking first buttonon clicking second buttonon clicking third buttonon clicking fourth button

Styling

I didn't attempt to do any styling in this exercise but if I can figure out how to apply styling the elm way, that's really all I need for this widget. Planning to look into the following libraries to dig further into styling with Elm.

First impressions:

  • Honestly, the whole exercise took me less than 10 minutes and this is the first time I'm writing any elm code
  • I was very impressed with how little boilerplate is needed - imagine doing this with Redux.
  • I used create-elm-app to quickly bootstrap a server with hot reloading
  • The compiler was super helpful and pleasant to work with. It was like having a friendly pair programmer watching out for errors every step of the way
  • I like the whole everything colocated in the same file. I can see how even when this is more complex, it can help to keep all related files together
  • The Elm debugger with time traveling is super cool
  • Elm formatter really helps with learning and trying to do a bunch of things BEFORE the compiler steps in to help you
Elm debugger

Iteration one: Start Init function with a Msg

A small improvement I wanted to make was to start with an empty model but immediately trigger an update so the initial state is equal to a default selection rather than an empty page.

So I want this:

Initial state with first button clicked

instead of an empty blank state like this:

Empty initial state

Essentially, I want to set the initial state of the app to be equivalent of an Update triggered by the user. Since that logic is already captured in the update function, I would simply like to trigger init with a Msg Coffee.

How to send command on init in Elm? 🤔

I attempted doing the following but that didn't work and resulted in a type mismatch error. I tried fudging around with the type signature but couldn't get it to work.

init : ( Model, Cmd Msg ) init = ( update Coffee { subject = "" , copy = "" } , Cmd.none )

I haven't run into the idiomatic way in Elm to model this in my research so far. Should the initial state to be the exact state I want (not empty strings) or trigger an update in such a scenario?

I found this reddit thread that helped shed some light. Looks like you have to split that functionality separately and initialize with a function call instead of an empty model.

setCoffee : Model -> Model setCoffee model = { model | subject = "Grab coffee", copy = "I'm down to grabbing coffee talking about your startup, a new opportunity or just plain banter." }

That means, I have to change the update method to look like below. Note the coffee branch is calling the function we just created instead of setting the values directly as before.

update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Coffee -> ( setCoffee model, Cmd.none ) Meetup -> ( { model | subject = "Organize a meetup", copy = "I love participating in interest-based comnunities in real life. Running a meetup can be hard and I'd love to help in anyway I can." }, Cmd.none ) OpenSource -> ( { model | subject = "Open source help", copy = "I am always looking to contribute to projects in meaningful ways. I enjoy coding and writing clear technicaly documentation." }, Cmd.none ) Talk -> ( { model | subject = "Request a talk", copy = "I'm seeking opportunities to speak at international conferences and local meetups this year. Definitely reach out if you see a good fit." }, Cmd.none )

Also, needed to change the init method as follows:

init : ( Model, Cmd Msg ) init = ( setCoffee { subject = "" , copy = "" } , Cmd.none )

This essentially means, the initial state is calling a function setCoffee with the base state we started with and will instantiate the app with what the function returns. Here's the full final program. You can also view it in action here

module Main exposing (..) import Browser import Html exposing (Html, button, div, text) import Html.Events exposing (onClick) ---- MODEL ---- type alias Model = { subject : String , copy : String } init : ( Model, Cmd Msg ) init = ( setCoffee { subject = "" , copy = "" } , Cmd.none ) ---- UPDATE ---- type Msg = Coffee | Meetup | OpenSource | Talk setCoffee : Model -> Model setCoffee model = { model | subject = "Grab coffee", copy = "I'm down to grabbing coffee talking about your startup, a new opportunity or just plain banter." } update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of Coffee -> ( setCoffee model, Cmd.none ) Meetup -> ( { model | subject = "Organize a meetup", copy = "I love participating in interest-based comnunities in real life. Running a meetup can be hard and I'd love to help in anyway I can." }, Cmd.none ) OpenSource -> ( { model | subject = "Open source help", copy = "I am always looking to contribute to projects in meaningful ways. I enjoy coding and writing clear technicaly documentation." }, Cmd.none ) Talk -> ( { model | subject = "Request a talk", copy = "I'm seeking opportunities to speak at international conferences and local meetups this year. Definitely reach out if you see a good fit." }, Cmd.none ) ---- VIEW ---- view : Model -> Html Msg view model = div [] [ button [ onClick Coffee ] [ text "Coffee" ] , button [ onClick Meetup ] [ text "Meetup" ] , button [ onClick OpenSource ] [ text "Open Source" ] , button [ onClick Talk ] [ text "Talk" ] , div [] [ text model.subject ] , div [] [ text model.copy ] ] ---- PROGRAM ---- main : Program () Model Msg main = Browser.element { view = view , init = \_ -> init , update = update , subscriptions = always Sub.none }

Next steps

Clearly, this is a very scoped exercise and the design and functionality of what I wanted to do was fully fleshed out. I wonder how Elm would be for prototyping when I'm just playing around with the UI and attempting a bunch of different things without a clear idea. Also curious to see how it scaled to more complex projects. Next thing I want to learn is how do I integrate with Next JS / Gatsby type ecosystem.

Built with passion...

React

Used mainly for the JSX templating, client-side libraries and job secruity.

Gatsby

To enable static site generation and optimize page load performance.

GraphQL

For data-fetching from multiple sources.

Contentful

CMS to store all data that is powering this website except for blogs, which are markdown files.

Netlify

For static site hosting, handling form submissions and having CI/CD integrated with Github.

Images

From unsplash when they are not my own.

..and other fun technologies. All code is hosted on Github as a private repository. Development is done on VS-Code. If you are interested, take a look at my preferred dev machine setup. Fueled by coffee and lo-fi beats. Current active version is v2.12.1.

</VR>