Lessons from The Elm Architecture, applied to the Terminal

The Elm Architecture consists of three parts - the model, view and update.

The model is the state of the program.

The view is a way to turn the model into a visual representation (in Elm’s case html).

The update is a way to change the state, based on messages.

To demonstrate how this can be applied to the terminal, let us consider a small ncurses program - an interactive one dimensional grid with an improvised cursor.


The first thing we can apply is the concept of the model, or state.

What should our state be?

The definitition of state, as taken from the Cambridge Dictionary, is the following:

a condition or way of being that exists at a particular time

Therefore, to determine what the state of our program should be, we need to think about the data in our program that can change over time.

In our one dimensional grid, the only thing can that change over time is the position of the cursor.

There are four cells in our grid, and the cursor can only be at one cell at any given time.

Therefore the state, or model, can be represented by an integer.

We want the cursor to be at a specific cell when the program starts, so we initialize the state with the integer 0.


int state = 0;

The next concept to apply is that of the update.

We will update the state based on user input.

The only input we will consider is that from the left and right keys, represented in ncurses with the key codes KEY_LEFT and KEY_RIGHT.

We have a boundary condition at s=0, which means that if the the input corresponds to KEY_LEFT then s should be set to 3 - and an analagous condition at s=3, where s shoud be set to 0 if the input matches the KEY_RIGHT key code.

Once the update is done we return the state.

The body of our update function should look something like this:

{
  switch (ch) {
  case KEY_LEFT:
    if (state == 0)
      state = 3;
    else
      state = state - 1;
    break;
  case KEY_RIGHT:
    if (state == 3)
      state = 0;
    else
      state = state + 1;
    break;
  }
  return state;
}

We then reset the state to be equal to the return value of the update function.


The final concept is that of the view.

We simply need to turn the state into a visual representation.

We want to display the grid cells and an improvised cursor.

The cursor will simply be a highlighted grid cell, so we render our view by iterating through the cell number and checking if the state (or cursor position) is equal to the grid cell being rendered.

If the number of grid cell being rendered is equal to the state, then we want our cell text to be bold and highlighted.

This can be done with the ncurses attributes A_BOLD and A_STANDOUT.

If the grid cell being rendered is not equal to the state, then we simply give the cell the attribute A_NORMAL.

We have to remember to reprint the text and refresh:

{
  for (int i=0; i<4; i++)
    if (state == i) {
      /* Set the grid cell to have the attributes */
      /* A_BOLD and A_STANDOUT, re-print text and */
      /* refresh */
    }
    else {
      /* Set the grid cell to have the attribute */
      /* A_NORMAL, re-print text and */
      /* refresh */
    }
}

After the state has been initialized, to continously respond to user input and update the view, we can call the update and view functions within an event loop.


References

“Meaning of state in English” https://dictionary.cambridge.org/dictionary/english/state

“The Elm Architecture” https://guide.elm-lang.org/architecture/

The ncurses documentation, which can be read by running the command man ncurses inside a terminal.

The ncurses documentation can be obtained on a Debian system by installing the ncurses-doc package, or on a CentOS/Redhat/Fedora systems by installing the ncurses-devel package.