Software developer

Developer of webapps and more since 2016

01 Apr 2020

434
Elm - pros and cons

Tags

Disclaimer :

I am not Evan Czaplicki, I am not the creator of Elm, and I have a profoud respect of his work. Very nice work Evan !

For crafting a webapp, you have a bunch of options. Here is a (non exhaustive) list of opensource languages for webapps:

  • Javascript
  • Typescript
  • Elm
  • ScalaJS
  • ReasonML
  • CoffeeScript
  • PureScript

Here we will focus on Elm, a functional language which compiles to javascript.

Elm is designed since 2012 by Evan Czaplicki.

It's core features are :

No runtime exceptions means, if it compiles, it works. (Meaning your program is safe and will work without crashing, it does not mean your interface won't be buggy in some way)

Architecture

Here is a simple example of an Elm component without routing :


import Browser
import Html exposing (Html, button, div, text)
import Html.Events exposing (onClick)

main =
  Browser.sandbox { init = 0, update = update, view = view }

type Msg = Increment | Decrement

update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

Browser.sandbox will gather the component's components into an exported main

update is the function that updates your data Model (aka state in redux)

Redux mechanism is inspired by Elm architecture.

update function takes a Msg and the model in arguments, and returns the model

view (aka render in react) is the rendering template of the component. It takes the model in argument and returns Html, created by function (like react)

As explained in this schema : the flow is is that way :

Dom -> Html event -> updates Model -> triggers render of view

like in react/redux, but way simpler !

In the function update, you can see a case - of with two function : Increment and Decrement. These are functions you can directly call in the view.

Adding an argument to the Increment/Decrement handlers would look like this :

type Msg  =
    AddNumber Int
    | SubtractNumber Int

type Model = Int

update : Msg -> Model -> Model
update msg model =
    case msg of
        AddNumber number ->
               model + number
        SubtractNumber number ->
               model - number


view : Model -> Html Msg
view model =
     div []
    [ button [ onClick (AddNumber 10) ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick (SubtractNumber 10) [ text "+" ]
    ]

This way you add or subtract a number to the model by calling it with a number.

No runtime exceptions

The core language of Elm is based on a thing : If it compiles, it works.

In javascript there is null and undefined.

In Elm, there is no null or undefined.

To handle errors in Elm, there are two types: Maybe and Result

The Maybe type has this signature :

type Maybe a
    = Just a
    | Nothing

Usage :


let 
    myName = Maybe "Cyprien"
in
    case myName of 
       Just name -> 
                "Hi my name is " ++ name
       Nothing ->
                "User didn't gave his name" 

-- "Hi my name is Cyprien"

The Maybe type contains the ok value but does not explains why it failed.

The Result type has this signature :

type Result error value
  = Ok value
  | Err error

Usage :


let
   token = Ok "jyr3foiz2ve3"
in
   case token of
      Ok value ->
          "Token : " ++ token

      Err err ->
          "Error: " ++ err
--  "Token : jyr3foiz2ve3"

Validating incoming data

Elm has a library for validating the incoming json from the remote server.

If the json matches the schema, it continues, else it fails and returns a decode error which may be used to display a message to the user.

I'm not in the position to give advices (I guess I'm on the good side of the Dunning-Kruger effect) but IMHO, I think it would be simpler for the developper : it requires to create decoders for each object and sub Object, array and field of the incoming data. It would be easier if it could be as in Rust, where you just have to type the function that decodes the json and it is infered and you receive the type that you infered, or a deserialization error.

Package manager

Elm has a package manager. Packages are accessible at package.elm-lang.org

Writing a package for Elm is relatively tedious, described in this blog post

  • You cannot expose native javascript code of the browser.
  • You need to write a documentation for each function exposed in the package.
  • The documentation for the package is automatically generated via a command into a json file from the documentation and the types of the functions
  • Packages are published from command line and centralized at package.elm-lang.org

When you install a package, it is cached at ~/.elm/version/packages/

So when you reinstall it, its version is written in the project's elm.json file and your program goes find it there when he needs it.

This means no more gigabytes of packages for each project like node modules.

Pros

Here is my list of pros of the language :

  • no runtime errors
  • simplified architecture and store management !
  • light syntax
  • clear error messages
  • easy refactor
  • lightweight project dependencies

Cons

  • no browser api in current version (2020-03) (webstorage, url management, localDB..) You need to use ports for that
  • no access to window object, you need to use the Browser Api or ports or flags for that.
  • types syntax can be disturbing
  • it's hard to make it compile at the beginning :x
  • Most of the time, you will need to implement your needs by yourself because the community is slowly growing and the packages availability is limited at that time. But every of the requirements to build those needs are available.
  • I find the decoder function of the json a bit tricky, it's a lot of code to write..

Useful links

My last word in conclusion :

Make it compile, and it won't crash !