Exploring Elm

Giving the Web World its Leviathan

 


 

So, you're not sick and tired of hearing about functional programming? Well then get a load of this...

...ELM!

  It's incredibly easy to dump on JavaScript. Where do we even begin? You can basically do whatever you want in that language. Compare a number to a string? Sure, why not. Add random s**t onto an object wherever and whenever? Please sir, may I have another? This is simultaneously the frustrating and useful philosophy of JS, its anarchic system. This allows us great freedom in writing applications, but usually that 'freedom' ends up biting us in the a**. Thomas Hobbes uttered the memorable phrase (in reference to life without governance),

...solitary, poor, nasty, brutish, and short.

I like to think he would say the exact same thing about life in the JavaScript ecosystem if he were alive -and developing- today. So then what is our answer to this post-Bane Gotham City of a development system?

  The answer isn't easy if you stick to JavaScript as your fundamental tool. You have workarounds, sure. TypeScript, Rambda, ImmutableJS, the list goes on and what you're left with is a project that, at the end of it, is still susceptible to its foundational shortcomings. You're still in JS-land, afterall. In order to avoid these deficiencies there's one obvious solution: find a new language. But that's a little hard because the Frontend world is nearly-synonymous with JavaScript. Enter Elm.

Elm is a domain-specific programming language for declaratively creating web browser-based graphical user interfaces. Elm is purely functional, and is developed with emphasis on usability, performance, and robustness. It advertises 'no runtime exceptions in practice,' made possible by the Elm compiler's static type checking. -Wikipedia

This excerpt gives a nice TL;DR of the language. You're looking at a safer alternative to JavaScript. Think Haskell, but for the Frontend ecosystem, and minus some of the difficult academic-oriented concepts. With static type checking we automatically get an entire species of bugs wiped out almost immediately. And with a purely functional approach we get a safer and cleaner state management. No more overwriting variables! This approach is going to be new to someone who's used to an imperative/dynamic language. But you're smart. You'll figure it out. Let's explore!

 


 

  Rather than going through the litany of beginner-necessary language details (types, how to do a function, etc.), I'll refer the reader to the Elm documentation. This article will simply explore some applications of Elm's lanuage constructs with a simple example.

The first fantastic thing about Elm is that it has a REPL (read-eval-print-loop). This is common to many languages/environments (Python and Node, to name two) and is a really handy thing to have, especially for a beginner who's just messing around with things. This means I can do stupid stuff just to see if it works. Just go into your console and (after installing Elm) enter elm-repl

> doubleIt n = n * 2
<function> : number -> number

> doubleIt 12
24 : number

Sweet. I can write a function that doubles a number. That's a fun party trick, but it hardly replaces React. Let's trudge forth...

How do we get this working with HTML? First, let's create a .elm file

-- ItCanBeDone.elm ('--' is a comment in Elm '{-- --}' is a block comment)

{--
  By convention we name the file and the module the same thing. `expose`
  here means what, from this file, we want to "show" to other files
  that might import it. In this case we want to show everything.
--}
module ItCanBeDone exposing (..)

{--
  Here we're importing the `Html` module, specifically its `text` function.
--}
import Html exposing (text)

main =
  text "¡Si se puede!"

Compilation of this file is as easy as typing elm reactor from your command line, going to localhost:8000, clicking your file in the menu, and reveling in how you just 'hacked the planet'. That still isn't what we want though. What we want is to not have to go through the Elm ceremony of visiting something we didn't create to see what we did. That's possible via elm-make. Let's try

$ elm-make ItCanBeDone.elm --output index.html

By checking our project directory we can see that index.html is now created. Let's run a simple server and get it up and running. I used node's http-server and visited localhost:8080/index.html and, sure enough, there's my output. Nice. We have a way to get Elm onto our browser. Let's keep hacking away.

Now...

Let's get some actual HTML elements onto the page. All the below example does is put two buttons onto the page and increment/decrement a visible counter. That's it. That's pretty simple, but this tiny program exposes some core Elm concepts to us.

 0 module OurFile exposing (..)
 1
 2 import Html exposing (beginnerProgram, div, button, text)
 3 import Html.Events exposing (onClick)
 4
 5 -- Model
 6 model = 0
 7
 8 type Msg = Increment | Decrement
 9
10 -- Update
11 update msg model =
12   case msg of
13     Decrement ->
14       model - 1
15
16     Increment ->
17       model + 1
18
19 -- View
20 view model =
21    div []
22        [ button [ onClick Increment ] [ text "+" ]
23        , div [] [ text ( toString model ) ]
24        , button [ onClick Decrement ] [ text "-" ]
25        ]
26
27 main =
28   beginnerProgram { model = model, view = view, update = update }

Above we see three commented sections, Model, View, and Update. This is almost synonymous with a typical MVC approach, where we have a clear separation of how our state is modeled, how our state is updated, and how our view is shown. In this instance it's straightforward. But, one question pops into mind, if there are no side effects in purely-functional code how is Elm handling models update? Isn't that variable reassignment?

Ah! Congrats if you also saw that. Elm's update function (line 11, above) isn't mutating anything, it's creating a new state. That function takes two params: action and state. Our action in this case has the potential to be either Increment or Decrement and our state... well, that's just model. This is interesting. Like Redux this implies that we have the potential for time-travel debugging. And guess what? We do have that capability in Elm! Check it out at some point on GitHub. That's the kind of functionality immutable data and non-mutated states give us!

Speaking of line 11, there's also something interesting that really hasn't made its way into the JS world yet. Do you see it? We're essentially doing a switch-case off of a type. This is called pattern matching and it's really useful. If you're familiar with Redux this looks vaguely familiar. It's sorta like how reducers work. Let's see a reducer example to really draw the parallels

export default (state = false, action) => {
  switch (action.type) {
    case 'OPEN':
      return true
    case 'CLOSE':
      return false
    default:
      return state
  }
}

// Where `action` might look like
{ type: 'OPEN' }

That looks like a poor-man's version of what Elm accomplishes, right? In JS we have to create an object that has a string which identifies the object it's attributed to. That's kinda like walking around wearing a T-shirt that has your name on it. Useful, until you realize that you can say your own name. That's JavaScript, a person who wears a shirt with their own name on it because they can't utter their own identity. And that's the power of pattern matching. We don't have to create extra attributes on our Msg (our action param) for the sole purpose of self-identification, we can just update state based on the type of the Msg.

On top of this shiny, new pattern matching ability we also get the safety checks of the compiler (static type checking, etc). Go ahead, misspell Decrement and run elm-make. What happens? Well, it freaks out quite frankly

Cannot find variable `Decremnt`

20|         [ button [ onClick Decremnt ] [ text "-" ]
                               ^^^^^^^^
Maybe you want one of the following?

    Decrement

Detected errors in 1 module.

Cool. We'd have to pull in TypeScript or Flow to get this sort of safety in JavaScript. What about type checking? How does that work? Well, if you're familiar with Haskell, it's very similar. First we define our function with a type annotation

addTwoNumbers : Int -> Int -> Int
addTwoNumbers 3 "2" // ERROR!

Unlike JavaScript, which would give you 32 as an answer to the above, Elm won't compile because of a type mismatch. Good, because math. Elm can also infer the expected types of your functions too, which helps if you want to iterate quickly.

 


 

Elm is definitely something to try out. It's certainly an immature ecosystem (at least compared to the gargantuan JavaScript ecosystem) and some of the simple aspects of frontend development -like adding markup to the page- are bewildering (see lines 21 - 25 above if you don't know what I'm talking about), but if you want to be absolutely certain that your state is kept clean and you don't get any weird "I added this string to this number for you cuz that makes sense" bugs, Elm certainly has that (and a lot more) in the bag.

Certainly check out the Elm tutorial to see what purely functional frontend development looks like. Oh! And I see that it has a Markdown library... maybe it's time to rewrite my site... in ELM.

 

Happy Hacking.

comment

Comments