A couple months ago I looped back to playing with Elm to try and build a clone of TeuxDeux. It felt like I was floundering a ton, although I also came close-ish to finishing the project.

Not another Todo List (NATL)

Yeah. I know. Y Tho? Why another todo list? Well, in reality, I told someone I’d make them an open source version of TeuxDeux that they wouldn’t have to pay for. But dammit. DAMMIT. I could sigh so hard I might pass out. I made a (half-serious) promise, let me tell you. No more boring-side-project-demo CRUD apps (especially ones with authentication). I’m a sucker though. I fell for it like a fool falls on a grape.

Ok, let’s leave this behind. The domain was boring, but the prospect of playing with Elm wasn’t. But when playing with Elm got boring or frustrating, I really wished I had chosen to build something I was interested in (and I have LISTS of things, because obviously, I’m very interesting, and everything I do has always been interesting up to this point in time.)

Onto an overview of the exp:

First iteration: Stubbing the Client

Second Iteration: The Backend

Optimistic / Pessimistic HTTP API requests

Somehow this app was the first time I started to think about this idea of “optimistic” or “pessimistic” HTTP server requests. Here’s an overly verbose documentation chunk from the codebase:

  • lots of client-side changes need to update things in the database.
    • Ex: completing a task needs to update the DB.
    • The response is the todo that got updated
    • this todo replaces the old one in the model.
    • so there’s a possibility there will be a lag, but as of now, it’s pretty fine.
    • Syncing model state with DB is a bit annoying but for now many Update actions are ending up making minimal changes and then running an HTTP command who’s handler (another update) will be responsible for triggering the necessary re-renders
    • EDIT: this is a “pessimistic” update vs optimistic link

The last point links to a great article regarding this topic and explains things really nicely (and with Clojurescript and Re-frame, which are rad).

I ended up making all requests pessimistic at first, mostly because I think at the time it seemed simpler to do with Elm – that is, I wouldn’t have to think about modifying the model in one of the Update branches, and then re-replace the item that changed when the HTTP request succeeded (or perhaps I could have filtered to see that the updated data was there and correct and not replaced it? I don’t know.). If this sounds confusing and wordy, that’s because it is. This problem, although probably trivial to most, was annoying to think about, and I probably went in circles a bit because of it.

Breaking Up with the Update function

After a while, I felt like I was going in circles with Elm and got discouraged the more and more I read my code. I split up my Update function into different files, which felt necessary as the file grew in length but it was also a bit clunky to navigate update branches across multiple files. I grouped updates related to successful HTTP results in HTTP.elm files by domain and then referenced it in the master Update.elm file. All around things started to feel verbose and clunky.

Inside the grouped-by-feature (in a two-feature project, ha; maybe that was a mistake) Http.elm file, I grouped things by HTTP verbs-as-request and then grouped the corresponding command and respective response. I think. Honestly, it feels like a garbage pile, and I think I had a 5% clue as to what I should be doing. To some, how things are organized don’t really matter. I like things organized, but I didn’t know if I was really organizing at that point.

Example: Http update code:

    updateSingleUrl : Todo -> String
    updateSingleUrl todo =
        prefix ++ (toString todo.id)
    
    
    updateReq : Todo -> Http.Request Todo
    updateReq todo =
        Http.request
            { body = todoEncoder todo |> Http.jsonBody
            , expect = Http.expectJson todoDecoder
            , headers = []
            , method = "PATCH"
            , timeout = Nothing
            , url = updateSingleUrl todo
            , withCredentials = False
            }

Above I’m setting up the HTTP request

    updateCmd : Todo -> Cmd Msg
    updateCmd todo =
        updateReq todo
            |> Http.send Msgs.HttpOnTodoUpdate
    
    
    onUpdate : Model -> Result Http.Error Todo -> ( Model, Cmd Msg )
    onUpdate model res =
        let
            _ =
                Debug.log "onupdate" res
        in
            case res of
                Ok todo ->
                    -- loops through all todos and replaces the one with the id with the updated.
                    let
                        updateTodos t =
                            if t.id == todo.id then
                                todo
                            else
                                t
                    in
                        { model | todos = RemoteData.map (\l -> List.map updateTodos l) model.todos }
                            ! []
    
                Err error ->
                    -- TODO!
                    model ! []

Then I have the actual command and the corresponding function to run in elm’s Update branch.

(Not) Upgrading to 0.19

Shortly after Elm 0.19 came out I briefly played with trying to get the application to compile. I followed the compiler around for a while, but I found that the changes were altogether too breaking and I was not motivated enough to revisit the pet project. I found I was particularly off-put at the idea of re-jigging how I had setup the todo domain with Elm’s concept of Date and Time, which have since changed with new Types and has deprecated the Day type (afaik).

Elm 0.19 looks sweet, but I don’t think I’d really need any of the new things. Smaller bundles are always cool though!

When would I use Elm in a production setting?

I’ve been asked this before and I’m waffling between a few different answers.

Every time I think back to some of my jobs where I’ve been consistently haunted by undead bugs, have had mutable state cause problems etc, I’ve idealized Elm as a solution. If you’re building a pretty CRUD-ish app, I’ve been prone to thinking Elm is a great solution.

Not only that but in medium-ish teams, I enjoyed the idea of Elm as a language that enforces consistency. Everyone would be on the same page in this dev-fantasy; all code would be formatted the same, everyone follows the Elm Architecture, we’d all have standing desks, our stand-ups would light candles and we would worship the compiler, I would be blessed by the lords of Haskell with knowing what a type class is and then I’d be able to complain about Elm not having them etc etc etc. All one big family you know?

Once I actually started writing Elm, my opinion changes a bit. Managing state with forms is simple, but it’s cumbersome. You can group data in records, but I found updating nested records to be annoying. I’m sure if I had a more advanced guide/mentor, they might have come up with some nifty pure functions to solve that, but it was just me ( I think there are some good form libraries out there for Elm too, IIRC. )

The organization of files is another thing. I’ve seen this come up in the community and I believe Evan did a talk on “The Life of a File” that was helpful. On my own though, I still feel like I got pretty muddled.

I think my biases are pretty strong here. I’ve been writing a lisp for about a year, and have very little experience with types. I don’t like talking about the value of writing static vs dynamic languages (at least, I’ve never enjoyed / learned much from people arguing about which is better). I would rather just build something, or learn something new. I’m still curious about typed langs and I’m looking forward to finding another reason to take a crack at writing a typed language more seriously than a project like this.

This was a fun project, and to be honest, writing in Elm had me pretty possesed for a little while. I recall staying up till 12 or 1 for the first few days. While it did get more and more confusing, I think I was mostly doing whirlpools in the echo chamber.

I’m looking forward to returning to Elm when I inevitably loop back to it again to continue to try and get more acquainted with pure functional programming. It still feels like the most acessible way to learn the topic (especially having a front-end background).

Collected Links:

Here are some links that I ended up visiting along the way: