Lessons learned from my first 10k LOC in Elm
Tags: elm, functional, webdev, showdev
Published: 2019-10-24 15:16:20

I have been working on a personal project of mine for the last couple of months that has the frontend written in Elm. So far everything is going great and the project is around 10k lines of code.

I have noticed a few reoccurring patterns of mine that I have learned so far and want to share, so here are 5 things I have learned:

## 1. Decoding Empty Lists & Strings

When I first started the project, I had a lot of types with fields declared like this:

```elm
type alias Something =
  { name : Maybe String
  , stuff : Maybe (List String)
  }
```

and the way I was decoding these values from my API was like this:

```elm
import Json.Decode as D
import Json.Decode.Pipeline exposing (optional)

D.succeed Something
  |> optional "name" (D.nullable D.string) Nothing
  |> optional "stuff" (D.nullable (D.list D.string)) Nothing
```

This was fine at first, but I noticed that after awhile I was repeating logic inside my update functions to check **if the `name` field was an empty string, or if the `stuff` field was an empty list**, and if it was, change the value to `Nothing` instead of `Just ""` or `Just []`.

So after awhile, I thought it would be more efficient to check for empty strings and empty lists during the decoding phase, so I came up with two decoder helper functions, `decodeNonEmptyString` and `decodeNonEmptyList`:


```elm
decodeNonEmptyString : D.Decoder (Maybe String)
decodeNonEmptyString =
  D.andThen (notEmptyString >> D.succeed) D.string

decodeNonEmptyList : D.Decoder a -> D.Decoder (Maybe (List a))
decodeNonEmptyList l =
  D.andThen (notEmptyList >> D.succeed) (D.maybe (D.list l))

notEmptyList : Maybe (List a) -> Maybe (List a)
notEmptyList =
  Maybe.andThen (\l ->
    if List.isEmpty l then
      Nothing
    else
      Just l
  )

notEmptyString : String -> Maybe String
notEmptyString s =
  if String.isEmpty s then
      Nothing
  else
      Just a
``` 

So now my original decoders use these new utilities like this:

```elm
D.succeed Something
  |> optional "name" decodeNonEmptyString Nothing
  |> optional "stuff" (decodeNonEmptyList D.string) Nothing
```

And I don't need to check for emptiness anymore in my update functions 🎉

## 2. Flatter pattern matching

This one might seem really obvious, but took me awhile to realize that doing this results in flatter pattern matching functions.

If I have a type like `RD.WebData (Maybe (List Person))`, the way I original matched on it was like this:


```elm
case response of
  RD.NotAsked -> div [] [text "Not Asked"]
  RD.Loading -> div [] [text "Loading"]
  RD.Failure e -> div [] [text "Error"]
  RD.Success maybeData ->
    case maybeData of
      Nothing -> div [] [text "No data"]
      Just data -> div [] [text "Data!"]
```

But I realized that I could flatten this into one case statement instead of 2 separate ones like this:

```elm
case response of
  RD.NotAsked -> div [] [text "Not Asked"]
  RD.Loading -> div [] [text "Loading"]
  RD.Failure e -> div [] [text "Error"]
  RD.Success Nothing -> div [] [text "No data"]
  RD.Success (Just data) -> div [] [text "Data!"]
```

See? Much nicer!


## 3. Select Wrapper

This is one of the first abstractions I made when I started because I wanted an easy reusable way to use `select [] []` and found that the native one in Elm is not as nice as I hoped.

So I wrote a small wrapper called `viewSelect`:

```elm
viewSelect : Bool -> (String -> msg) -> List Option -> Html msg
viewSelect disabled changeMsg options =
  select
    [ onChange changeMsg
    , Html.Styled.Attributes.disabled disabled
    ]
    (List.map
      (\opt ->
        option
          [ value opt.value
          , Html.Styled.Attributes.selected opt.selected
          , Html.Styled.Attributes.disabled opt.disabled
          ]
          [ text opt.text ]
      )
      options
    )

type alias Option =
  { text : String
  , value : String
  , selected : Bool
  , disabled : Bool
  }


onChange : (String -> msg) -> Html.Styled.Attribute msg
onChange tagger =
  on "change" (D.map tagger Html.Styled.Events.targetValue)
```

Now I use `viewSelect` instead of the native `select` and I just need to pass in a list of `Option` instead. Every time a user changes the select box, the changeMsg fires with the new value.

## 4. ChangeField Pattern

In my app, I have a lot of forms, and every form needs to modify some field inside my model. The way I originally started doing this was:

```elm
type Msg
  = ChangeName String
  | ChangeAge String
  | ChangeHeight String
  | ChangeWeight String

update msg model =
  case msg of
    ChangeName name -> ({ model | name = name }, Cmd.none)
    ChangeAge age -> ({ model | age = age }, Cmd.none)
    ChangeHeight height -> ({ model | height = height }, Cmd.none)
    ChangeWeight weight -> ({ model | weight = weight }, Cmd.none)
```

This was also fine at first, but my app has A LOT of forms and this was not scaling well. So instead of creating a new Msg type for every field, I created **one** Msg type for updating any field. And it works like this:

```elm
type Msg = ChangeField (String -> Person -> Person) String

update msg model =
  case msg of
    ChangeField setter content -> (setter content model, Cmd.none)
```

Now every time a field needs to change, instead of calling `ChangeName "Jason"`, I can now call `ChangeField setName "Jason"` where setName equals:

```elm
setName : String -> Person -> Person
setName content person =
    { person | name = content }
```

This allows me to create individual setter functions for each field and do any data manipulations from String into any type I need.

It simplifies my update function by reducing the amount of Msg types it can match on, and it nicely separates out my data manipulation logic for each field.

## 5. Posix Wrappers

This one is pretty simple really. I use Elm's `Time.Posix` type a lot around my app, so I needed functions to format, manipulate and display Posix in my UI. I came up with a few helpers for this and call it my HumanTime module:

```elm
humanDateTime : Zone -> Posix -> String
humanDateTime z p =
  humanDate z p ++ " " ++ humanTime z p

humanTime : Zone -> Posix -> String
humanTime z p =
  humanHour z p ++ ":" ++ humanMinute z p

humanDate : Zone -> Posix -> String
humanDate z p =
  humanMonth z p ++ " " ++ humanDay z p ++ ", " ++ humanYear z p

humanYear : Zone -> Posix -> String
humanYear z =
  toYear z >> String.fromInt

humanDay : Zone -> Posix -> String
humanDay z =
  toDay z >> String.fromInt

humanHour : Zone -> Posix -> String
humanHour z =
  toHour z >> String.fromInt

humanMinute : Zone -> Posix -> String
humanMinute z =
  toMinute z
      >> (\m ->
            if m > 9 then
              String.fromInt m
            else
              "0" ++ String.fromInt m
         )

humanMonth : Zone -> Posix -> String
humanMonth z p =
  case toMonth z p of
    Jan -> "Jan"
    Feb -> "Feb"
    Mar -> "Mar"
    Apr -> "Apr"
    May -> "May"
    Jun -> "Jun"
    Jul -> "Jul"
    Aug -> "Aug"
    Sep -> "Sep"
    Oct -> "Oct"
    Nov -> "Nov"
    Dec -> "Dec"
```

And that is it so far! I am sure I will learn even more in the next 10k LOC that I write and might even figure out better ways of doing these things that I just mentioned.

Elm is an amazing tool for creating frontends and I can't ever see myself going back to React or any other non-functional frameworks for personal projects.

If you liked this article, be sure to [follow me on DEV](https://dev.to/rametta) to see whenever I post new articles in your feed ✨ or [follow me on twitter!](https://twitter.com/rametta)