Simple code is different from simplistic code: Elm vs JavaScript
Tags: javascript, html, elm, webdev
Published: 2022-01-07 16:34:37

There are languages, frameworks, and libraries that strive to enable you to accomplish relatively complex tasks by writing a few lines of code. _JavaScript_ is a good example. To make an _http_ call to a page of my site using this language, you just have to write a single line:
```javascript
await fetch("https://segunda.tech/about")
```
Most people probably don't consider this code to be difficult or complex, but there may be hidden error scenarios that are not trivial to handle. To analyze this, I'll show you a small-page implementation using pure _JavaScript_ and discuss potential issues. Next I will show you how to implement the same solution using the [_Elm_ programming language](https://elm-lang.org) and analyze the same points.

## Exercise: Retrieving a list of Pokémon names

To exemplify the problem I want to discuss in this article, I implemented in _html_ and pure _JavaScript_ (using _Ajax_) the minimum necessary to display a list with Pokémon names. I used a service from [PokéAPI](https://pokeapi.co) for this. The endpoint for retrieving the list of the first 5 Pokémon is quite simple: just call the URL `https://pokeapi.co/api/v2/pokemon?limit=5` and the return will be a _json_ containing the result below.
```json
{
  "count": 1118,
  "next": "https://pokeapi.co/api/v2/pokemon?offset=5&limit=5",
  "previous": null,
  "results": [
    {
      "name": "bulbasaur",
      "url": "https://pokeapi.co/api/v2/pokemon/1/"
    },
    {
      "name": "ivysaur",
      "url": "https://pokeapi.co/api/v2/pokemon/2/"
    },
    {
      "name": "venusaur",
      "url": "https://pokeapi.co/api/v2/pokemon/3/"
    },
    {
      "name": "charmander",
      "url": "https://pokeapi.co/api/v2/pokemon/4/"
    },
    {
      "name": "charmeleon",
      "url": "https://pokeapi.co/api/v2/pokemon/5/"
    }
  ]
}
```
In this exercise the goal is to retrieve this data asynchronously and list on the _html_ page only the contents of the _name_ field (which is within _result_).

## Implementing a solution using pure _html_ and _JavaScript_

There are several ways to solve this problem using these technologies. Below I present my implementation.
```html
<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>List of Pokémons using HTML and JavaScript</title>
  <meta name="author" content="Marcio Frayze David">
</head>

<body>
  <p id="loading-message">
    Loading Pokémons names, please wait...
  </p>

  <ul id="pokemon-names-list">
  </ul>

  <script>
    
    (async function() {

      await fetch("https://pokeapi.co/api/v2/pokemon?limit=5")
        .then(data => data.json())
        .then(dataJson => dataJson.results)
        .then(results => results.map(pokemon => pokemon.name))
        .then(names => addNamesToDOM(names))

      hideLoadingMessage()
    
    })();

    function addNamesToDOM(names) {
      let pokemonNamesListElement = document.getElementById('pokemon-names-list')
      names.forEach(name => addNameToDOM(pokemonNamesListElement, name))
    }

    function addNameToDOM(pokemonNamesListElement, name) {
      let newListElement = document.createElement('li')
      newListElement.innerHTML = name
      pokemonNamesListElement.append(newListElement)
    }

    function hideLoadingMessage() {
      document.getElementById('loading-message').style.visibility = 'hidden'
    }

  </script>

</body>
</html>
```
The idea is that at the end of the _Ajax_ call, the loading message no longer appears and the list containing the Pokémon names is loaded within the _tag_ with the _id_ _pokemons-names-list_. I published [this page _on-line_ with _JSFiddle_](https://jsfiddle.net/4dL3wvqh) so you can see the expected behavior.

I know hardly anyone would write a code like that. I didn't use any _framework_ or external library and did some things that many would consider bad practices (such as putting _JavaScript_ code right in _html_). But even if i had implemented this solution with popular technologies like _[React](https://pt-br.reactjs.org)_, _[JSX](https://en.wikipedia.org/wiki/JSX\_(JavaScript))_ and _[Axios](https://axios-http.com)_, the potential problems I want to discuss here would probably still exist.

Looking at the code above, the questions I'd like you to try to answer are: 
- What will happen if a _timeout_ occurs in the _Ajax_ call?
- If the server returns a _status http_ of failure, what will happen? 
- If the server returns a valid _status http_ but the format of the returned content is different than expected, what will happen?

The above code does not answer any of these questions clearly. It is easy to visualize the "happy path", but any unexpected situation is not being treated explicitly. And while we should never put code into production that doesn't treat these scenarios, the _JavaScript_ language doesn't force us to deal with them. If someone on your team forgets to do the right treatment for one of these potential problems, the result will be a **runtime error**.

If your team is unlucky, these scenarios may appear when the code is already in production. And when that inevitably happens, it's likely to blame the developer who implemented that part of the system.

But if we know that this type of situation must be addressed, **why do languages, _frameworks_ and libraries allow this type of code to be written?**

## What is a **simple** solution?

There is a big difference between a solution being **simple** and being **simplistic**. This solution I wrote in _JavaScript_ is not simple. It's simplistic, as it ignores fundamental aspects of the problem in question.

Languages such as _[Elm](https://elm-lang.org)_ tend to force us to think and implement the solution for all potential problems. The final code will probably be larger, but it will provide assurance that **we will have no errors at runtime**, as the compiler checks and enforces the developer to handle all possible paths, leaving no room for predictable failures.

Another advantage of this approach is that we have a **self-documented code**. For example, the format of the expected return should be very clear, including which fields are required and which are optional.

## Implementing the same solution in _Elm_

Now let's look at a solution written in _[Elm](https://elm-lang.org)_ for this same problem. If you don't know this language (or some similar language, such as _[Haskell](https://haskell.org)_ or _[PureScript](https://purescript.org)_), you'll probably find its syntax a little strange. But don't worry, you don't need to fully understand this code to understand the proposal of this article.

First we need a simple _html_ file, which will host our page. This approach is quite similar to what is done when we use tools such as _[React](https://pt-br.reactjs.org)_ or _[Vue](https://br.vuejs.org)_.

```html
<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">
  <title>List of Pokémons using HTML and JavaScript</title>
  <meta name="author" content="Marcio Frayze David">
</head>

<body>
  <main></main>
  <script>
    Elm.Main.init({ node: document.querySelector('main') })
  </script>
</body>
</html>
```
This time our _html_ is just a shell. It will only load the application written in _Elm_ (previously compiled) and place its contents within the _tag_ _main_.

And finaly the interesting part: the code written in _Elm_. I will first list the code completely and then highlight and comment on some more relevant parts to the topic of this article.

```elm
module Main exposing (..)

import Browser
import Html exposing (..)
import Http
import Json.Decode exposing (Decoder)


-- MAIN


main =
  Browser.element
    { init = init
    , update = update
    , subscriptions = subscriptions
    , view = view
    }


-- MODEL


type alias PokemonInfo = { name : String }

type Model
  = Failure
  | Loading
  | Success (List PokemonInfo)


init : () -> (Model, Cmd Msg)
init _ =
  (Loading, fetchPokemonNames)


-- UPDATE


type Msg
  = FetchedPokemonNames (Result Http.Error (List PokemonInfo))


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of

    FetchedPokemonNames result ->
      case result of
        Ok pokemonsInfo ->
          (Success pokemonsInfo, Cmd.none)

        Err _ ->
          (Failure, Cmd.none)


-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none


-- VIEW


view : Model -> Html Msg
view model =
  case model of
    Failure ->
        text "For some reason, the Pokémon name list could not be loaded. 😧"

    Loading ->
      text "Loading Pokémons names, please wait..."

    Success pokemonsInfo ->
      ul []
        (List.map viewPokemonInfo pokemonsInfo) 


viewPokemonInfo : PokemonInfo -> Html Msg
viewPokemonInfo pokemonInfo =
  li [] [ text pokemonInfo.name ]


-- HTTP


fetchPokemonNames : Cmd Msg
fetchPokemonNames =
  Http.get
    { url = "https://pokeapi.co/api/v2/pokemon?limit=5"
    , expect = Http.expectJson FetchedPokemonNames decoder
    }


pokemonInfoDecoder : Decoder PokemonInfo
pokemonInfoDecoder =
  Json.Decode.map PokemonInfo
    (Json.Decode.field "name" Json.Decode.string)

decoder : Decoder (List PokemonInfo)    
decoder =
  Json.Decode.field "results" (Json.Decode.list pokemonInfoDecoder)
```
I've published [this page in the online editor _Ellie_](https://ellie-app.com/gkQczpWk8X3a1) so you can see this _webapp_ up and running. I recommend you try to change the code and see what happens. It's a great way to start experimenting with the _Elm_ language.

## Analyzing the implementation in _Elm_

I will not explain in this article all this code and the architecture behind the _Elm_ language. But I wanted to highlight some important parts for the context of the discussion of this article, starting with the definition of our _types_.

### Type definitions

```elm
type alias PokemonInfo = { name : String }

Model type
  = Loading
  | Failure
  | Success (PokemonInfo List)
```

In the code above is set a _type alias_, making it clearer to the person reading the code what is a _PokemonInfo_ (in this case, a structure with a field called _name_ of type _String_). This will also make life easier for our compiler by allowing you to handle the appropriate error when necessary and, during the build phase, be able to send more informative error messages.

Next, we define a _type_ named _Model_ that will be used to represent the current state of our application. In this example, our _webapp_ can be in one (and only one) of the 3 possible states:
- _Loading_: initial application state, indicating that the _http_ request is still being processed.
- _Failure_: represents a state of failure, indicating that there was a problem making the _http_ call to the server (which may be timeout, a parsing failure of the return message, etc.).
- _Success_: indicates that the request was performed and its return successfully converted.

Of the three defined states, only _Success_ has extra information associated with it: a list containing elements of type _PokemonInfo_. Note that this leaves no room for ambiguity. If we have a state of success, it's **mandatory** we have a list of _PokemonInfo_ defined and with a valid structure. And the opposite is also true: in case of failure, the list with the names of Pokémon will not be defined.

### The construction of the _html_ page

_Elm_ was one of the pioneers in using the concept of virtual _DOM_ and declarative programming in the development of _webapps_.

In the [architecture of _Elm_](https://guide.elm-lang.org/architecture), there is a very clear separation between the **state** of our application and what should be displayed on the screen. It is the responsibility of the _view_ function to mount, from the current state of our application, a representation of our virtual _DOM_. And every time the state changes (when, for example, you finish loading the data with Pokémon names) this function will be reevaluated and a new virtual _DOM_ created. 

In our example, this occurs in the following code snippet:
```elm
view : Model -> Html Msg
view model =
  case model of
    Failure ->
        text "For some reason, the Pokémon name list could not be loaded. 😧"

    Loading ->
      text "Loading Pokémons names, please wait..."

    Success pokemonsInfo ->
      ul []
        (List.map viewPokemonInfo pokemonsInfo) 


viewPokemonInfo : PokemonInfo -> Html Msg
viewPokemonInfo pokemonInfo =
  li [] [ text pokemonInfo.name ]
```

Here we have the declaration of 2 functions: the _view_ and a helper function called _viewPokemonInfo_. 

One advantage of using _types_ to represent the state of our application is that **always** that a piece of code is to use this _type_, the compiler will **compel** the developer to handle all possible states. In this case: _Loading_, _Failure_ and _Success_. If you remove the _Loading_ treatment from the _view_ function of our example, you will receive an error message similar to this when you try to compile the application:
```
Line 70, Column 3
This `case` does not have branches for all possibilities:

70|>  case model of
71|>    Failure ->
72|>        text "For some reason, the Pokémon name list could not be loaded. 😧"
73|>
74|>    Success pokemonsInfo ->
75|>      ul []
76|>        (List.map viewPokemonInfo pokemonsInfo) 

Missing possibilities include:

    Loading

I would have to crash if I saw one of those. Add branches for them!

Hint: If you want to write the code for each branch later, use `Debug.todo` as a
placeholder. Read <https://elm-lang.org/0.19.1/missing-patterns> for more
guidance on this workflow.
```

This brings more protection for the developer person to refactor the code and include or remove states from the application, making sure it won't fail to address some obscure case.

### Making a _http_ call

The code snippet below is responsible for making the _http_ call asynchronously and performing the parse of the return, turning it into a list of _PokemonInfo_.

```elm
fetchPokemonNames : Cmd Msg
fetchPokemonNames =
  Http.get
    { url = "https://pokeapi.co/api/v2/pokemon?limit=5"
    , expect = Http.expectJson FetchedPokemonNames decoder
    }


pokemonInfoDecoder : Decoder PokemonInfo
pokemonInfoDecoder =
  Json.Decode.map PokemonInfo
    (Json.Decode.field "name" Json.Decode.string)


decoder : Decoder (List PokemonInfo)    
decoder =
  Json.Decode.field "results" (Json.Decode.list pokemonInfoDecoder)
```

It's impossible to deny that this code is longer than a call to a _fetch_ function. But note that, in addition to making the call asynchronously, also validates and transforms the return into a _List PokemonInfo_, eliminating the need for any validation on our part.

At the end of the execution, a _FetchedPokemonNames_ message will be issued along with the result of the operation: either a list with names of Pokémon already decoded or a result representing that an error occurred.

It will be the responsibility of the _update_ function to receive this message and create a new state for the application.

```elm
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of

    FetchedPokemonNames result ->
      case result of
        Ok pokemonsInfo ->
          (Success pokemonsInfo, Cmd.none)

        Err _ ->
          (Failure, Cmd.none)
```

Once again, we must deal with all possible scenarios. In this example, there are two: 
- if _result_ is _Ok_, it means that our request has been successfully processed. A new state is then returned to our application, changing to _Success_, along with the list containing the Pokémon names.
- if the _result_ is _Err_, then we know that there was a problem during the request or when performing the _json_ parsing. A new application state is returned, changing it to _Failure_.

Whenever the return of the _update_ function is different from the previous state, the _view_ function will automatically be triggered again, then a new virtual _DOM_ is created and any changes will be applied to the screen. To better understand this process, you can read about _The Elm Architecture_ [on this page](https://guide.elm-lang.org/architecture).

## Conclusions

Although this article focused exclusively on _http_ requests and _JavaScript_, the same concepts are applied in many other scenarios, libraries, _frameworks_ and languages.

My intention is not to discourage the use of _JavaScript_. _[Elm](https://elm-lang.org)_ is a wonderful language, but I still use _JavaScript_ and _TypeScript_ in some _webapps_ and this is not the focal point of the problem. What I would like is that when you are consuming a function of your preferred language (regardless if it is a native function or from third-party libraries), that you always reflect: is there any scenario that this code is ignoring? Or, in other words, is this a **simple** or a **simplistic** solution?

Most importantly, when writing a new function, use a communication interface that encourages the person who consumes it to follow best practices. Even if she is following the path of minimal effort, she should be able to take care of all possible scenarios. Or, in other words, always follow the [Principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment).

---

Did you like this text? Checkout my other articles at: https://segunda.tech/tags/english