Separating application logic from the UI in Elm
Tags: elm, webdev
Published: 2019-07-08 10:42:03

---
title: Separating application logic from the UI in Elm
published: true
description: 
tags: elm, webdev
---

Yesterday I started to implement the logic for a [simple calculator](https://learn.freecodecamp.org/front-end-libraries/front-end-libraries-projects/build-a-javascript-calculator/) web app that I'm recreating.

![A simple calculator](https://thepracticaldev.s3.amazonaws.com/i/4t9yzd24schmve9udmjg.png)

One of my goals in building it is to use the features of Elm to cleanly separate the application logic from the UI.

I believe I achieved what I was going for and that's why I'm so excited to share my progress with you.

## So what did I do?

I [made a data structure](https://www.youtube.com/watch?v=x1FU3e0sT1I) in a `Calculator` module with the following public API:


```elm
module Calculator exposing
  ( Calculator, Key(..), Operator(..)
  , new, process

  , Display
  , toDisplay
  )
```

**N.B.** *The implementation details aren't important right now.*

`Calculator` is an [opaque type](https://medium.com/@ckoster22/advanced-types-in-elm-opaque-types-ec5ec3b84ed2).

The `new` function creates a `Calculator`. `process` takes a `Key` and a `Calculator` and returns an updated `Calculator`. And, `toDisplay` allows you to observe the results produced by a given `Calculator`.

For e.g.

```elm
Calculator.new
  |> Calculator.process (Digit 1)
  |> Calculator.process (Digit 2)
  |> Calculator.process (Operator Plus)
  |> Calculator.process (Digit 3)
  |> Calculator.toDisplay

{ expr = "12+3", output = "3" }
```

I experienced two major benefits. The first was that I was able to unit test (yes, [testing is important](https://www.youtube.com/watch?v=51O63Sb-Ae0) even in a typed functional language) my logic and gain confidence in my implementation.

Here are the tests:

```elm
module Test.Calculator exposing (suite)


import Expect
import Test exposing (..)

import Calculator exposing (Key(..), Operator(..))


suite : Test
suite =
  describe "Calculator"
    [ processSuite ]


processSuite : Test
processSuite =
  describe "process"
    [ describe "when nothing has been entered" <|
        let
          calculator =
            Calculator.new
        in
          [ test "pressing AC" <|
              \_ ->
                calculator
                  |> Calculator.process AC
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "", output = "0" }
          , test "pressing a digit" <|
              \_ ->
                calculator
                  |> Calculator.process (Digit 1)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "1", output = "1" }
          , test "pressing an operator" <|
              \_ ->
                calculator
                  |> Calculator.process (Operator Plus)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "", output = "0" }
          , test "pressing =" <|
              \_ ->
                calculator
                  |> Calculator.process Equal
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "", output = "0" }
          ]
    , describe "when a number has been entered" <|
        let
          calculator =
            Calculator.new
              |> Calculator.process (Digit 1)
              |> Calculator.process (Digit 2)
        in
          [ test "pressing AC" <|
              \_ ->
                calculator
                  |> Calculator.process AC
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "", output = "0" }
          , test "pressing a digit" <|
              \_ ->
                calculator
                  |> Calculator.process (Digit 3)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "123", output = "123" }
          , test "pressing an operator" <|
              \_ ->
                calculator
                  |> Calculator.process (Operator Plus)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12+", output = "+" }
          , test "pressing =" <|
              \_ ->
                calculator
                  |> Calculator.process Equal
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12=12", output = "12" }
          ]
    , describe "when a number and operator has been entered" <|
        let
          calculator =
            Calculator.new
              |> Calculator.process (Digit 1)
              |> Calculator.process (Digit 2)
              |> Calculator.process (Operator Plus)
        in
          [ test "pressing AC" <|
              \_ ->
                calculator
                  |> Calculator.process AC
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "", output = "0" }
          , test "pressing a digit" <|
              \_ ->
                calculator
                  |> Calculator.process (Digit 3)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12+3", output = "3" }
          , test "pressing an operator" <|
              \_ ->
                calculator
                  |> Calculator.process (Operator Minus)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12-", output = "-" }
          , test "pressing =" <|
              \_ ->
                calculator
                  |> Calculator.process Equal
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12=12", output = "12" }
          ]
    , describe "when a complete expression has been entered" <|
        let
          calculator =
            Calculator.new
              |> Calculator.process (Digit 1)
              |> Calculator.process (Digit 2)
              |> Calculator.process (Operator Plus)
              |> Calculator.process (Digit 3)
              |> Calculator.process (Operator Minus)
              |> Calculator.process (Digit 4)
        in
          [ test "pressing AC" <|
              \_ ->
                calculator
                  |> Calculator.process AC
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "", output = "0" }
          , test "pressing a digit" <|
              \_ ->
                calculator
                  |> Calculator.process (Digit 5)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12+3-45", output = "45" }
          , test "pressing an operator" <|
              \_ ->
                calculator
                  |> Calculator.process (Operator Plus)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12+3-4+", output = "+" }
          , test "pressing =" <|
              \_ ->
                calculator
                  |> Calculator.process Equal
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12+3-4=11", output = "11" }
          ]
    , describe "when an answer is given" <|
        let
          calculator =
            Calculator.new
              |> Calculator.process (Digit 1)
              |> Calculator.process (Digit 2)
              |> Calculator.process (Operator Plus)
              |> Calculator.process (Digit 3)
              |> Calculator.process (Operator Minus)
              |> Calculator.process (Digit 4)
              |> Calculator.process Equal
        in
          [ test "pressing AC" <|
              \_ ->
                calculator
                  |> Calculator.process AC
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "", output = "0" }
          , test "pressing a digit" <|
              \_ ->
                calculator
                  |> Calculator.process (Digit 9)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "9", output = "9" }
          , test "pressing an operator" <|
              \_ ->
                calculator
                  |> Calculator.process (Operator Plus)
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "11+", output = "+" }
          , test "pressing =" <|
              \_ ->
                calculator
                  |> Calculator.process Equal
                  |> Calculator.toDisplay
                  |> Expect.equal { expr = "12+3-4=11", output = "11" }
          ]
    ]
```

The second benefit was how trivial it became to implement the UI. I see many web apps that write extensive integration tests for UI code but that's because they couple the UI with the application logic. With proper separation of concerns you'd end up with more unit tests and much less integration tests (if any).

Here's the code that makes the UI work:

```elm
-- MODEL


type alias Model =
  { calculator : Calculator
  }


init : Model
init =
  { calculator = Calculator.new
  }


-- UPDATE


type Msg
  = Clicked Key


update : Msg -> Model -> Model
update msg model =
  case msg of
    Clicked key ->
      { model | calculator = Calculator.process key model.calculator }


-- VIEW


viewCalculator : Calculator -> Html Msg
viewCalculator calculator =
  let
    display =
      Calculator.toDisplay calculator
  in
    div [ class "calculator" ]
      [ div [ class "calculator__expr" ]
          [ if String.isEmpty display.expr then
              text (String.fromChar nonBreakingSpace)
            else
              text display.expr
          ]
      , div [ class "calculator__output" ] [ text display.output ]
      , div [ class "calculator__buttons" ]
          [ button [ class "r0 c0 colspan2 bg-red", onClick (Clicked AC) ]
              [ text "AC" ]
          , button [ class "r0 c2", disabled True ]
              [ text "÷" ]
          , button [ class "r0 c3", onClick (Clicked (Operator Times)) ]
              [ text "×" ]
          , button [ class "r1 c0", onClick (Clicked (Digit 7)) ]
              [ text "7" ]
          , button [ class "r1 c1", onClick (Clicked (Digit 8)) ]
              [ text "8" ]
          , button [ class "r1 c2", onClick (Clicked (Digit 9)) ]
              [ text "9" ]
          , button [ class "r1 c3", onClick (Clicked (Operator Minus)) ]
              [ text "-" ]
          , button [ class "r2 c0", onClick (Clicked (Digit 4)) ]
              [ text "4" ]
          , button [ class "r2 c1", onClick (Clicked (Digit 5)) ]
              [ text "5" ]
          , button [ class "r2 c2", onClick (Clicked (Digit 6)) ]
              [ text "6" ]
          , button [ class "r2 c3", onClick (Clicked (Operator Plus)) ]
              [ text "+" ]
          , button [ class "r3 c0", onClick (Clicked (Digit 1)) ]
              [ text "1" ]
          , button [ class "r3 c1", onClick (Clicked (Digit 2)) ]
              [ text "2" ]
          , button [ class "r3 c2", onClick (Clicked (Digit 3)) ]
              [ text "3" ]
          , button [ class "r3 c3 rowspan2 bg-blue", onClick (Clicked Equal) ]
              [ text "=" ]
          , button [ class "r4 c0 colspan2", onClick (Clicked (Digit 0)) ]
              [ text "0" ]
          , button [ class "r4 c2", disabled True ]
              [ text "." ]
          ]
      ]
```

**Key takeaway:** Let the UI drive a data structure and unit test the living daylights out of that data structure.

What do you think about this approach to implementing more complicated UIs?