Print Page

Monday, May 15, 2017

Elm One Day In

I've been interested in the Elm programming language for several months, reading and poking around some at code examples but with too many hobbies competing, only finally started really writing some yesterday.

I usually do a gaming related project to learn a new programming language or framework on my own time, because that is where I have software itches to scratch. This time I started with the simple die roll example and my first step was learning how to take a program where the model is a single Integer that gets updated to a record with two attributes, one the value rolled on the die and the other the size of die to roll. Finding and wrapping my head around the pattern to write to an attribute of a model was my first hurdle.

That set me up for a first serious project. I have been playing a lot of D&D 5e lately and my character sheet has gotten pretty messy. I've been procrastinating on the rewrite, so making a character sheet application that avoids getting messy and can export nice copies is a natural fit.  Also at work, we are thinking about rewriting the proposal submission application, which is a form of similar complexity to a D&D character sheet, with a complex nested data structure and a lot of options. I'd like to be able to say with confidence that we can handle it in Elm and be better off for having used it.

So, I hid away the die roller part of the UI with a style of display:none, but kept it around as the core of an in situ dice rolling part,  and started into the character sheet.

Design spec: This will be a web based single page application, well loosely SPA since a full character sheet for a spellcaster can run to four or five paper pages, and I'll probably be hiding portions away with paging or tabs of some sort. It will be a full character sheet that can handle multiclassing at least up to three classes. It might not go beyond the Player's Handbook in terms of what it supports with known classes and such, but I might allow for typing in some other options. The displayed form on the web should be usable in game from tablet or laptop, maybe phone but its an awfully big form to navigate on a phone. It will have localstorage persistence for offline use and probably use Dropbox or Google Drive as the personal server storage for accessing the same sheet from multiple devices, but I'm not planning to provide a server that users would depend on with all the authentication, uptime, and cost/monetization issues that entails. In that regard, it will autosave the sheet to at least localstorage as changes are made, with syncing to update remote storage whenever access is good. There will be some sort of list selector to choose the character to to load into the sheet, and something to delete unwanted characters. After a CSS design pass, it should print out a pleasant sheet to use. There will be nested structured data for things like weapons blocks and spell lists, but a lot of things that are simple lists like mundane possessions and encounters to remember will just be free text in text areas. It might be worthwhile to support markdown in text areas.

I spent most of my Sunday coding on it, and by the end had about 500 lines of good code that displayed and recorded edits to the model for pretty much all the top level fields. I intentionally put off the nested structures, knowing they would be more complex, and did zero to minimal CSS styling, leaving that for after things basically work.

I downloaded Atom for the editor and a few Elm related packages for it. It mostly worked for me but I was seeing errors from the package that was trying to auto run elm-format for me and did not go down the rabbit hole of working that out. Instead I manually ran elm-format once at command line later in the day, and mostly just tried to code close to what it would output, which slowed me a little bit. Should probably have run it more often. I had the project sitting in my Dropbox for easy access from other machines, so I did not initialize it as a git repo, but will probably do that in my next big session on it.

 It was a very productive flow, using elm-reactor as the dev server and the Chrome browser to see the results. I already love the Elm compiler, which made it very easy to clean up simple mistakes. The debugger was fun to play with, but not too necessary for the simple, repetitive sort of coding I was doing. The workflow for adding a plain text field was very straightforward. Add a line for then new attribute to the model with name and type. Add the new attribute to the initialization record to match the model, with the default value. Add an update message for it to the messages list. Define the function that responds to the message according to one of two patterns, one where it is just text, another where the value needs to be an integer. Add the field and any framing HTML like a list item or label to the view, with the onInput attribute of the field calling the updater message function. Save and go look at the web page to see if it worked or there are compiler errors to fix.  Adding a batch of them at once was easy, the compiler is good at pointing out missing ones in some section, mismatch typos, and similar problems, so I could move along quickly.  Using an editor with syntax color and bracket/parentheses matching support has the usual benefits. Besides something like fifty basic input fields, I did the first dependent attribute calculations to show the die roll modifiers based on the primary attributes

At that point I had two possible places to go - work out the beginnings of persistence to localstorage or start to tackle the deeper data structures beyond placement of H2 headers in the character sheet to show where they would go.

I went with trying to tackle one of the simpler data structures, the saving throws section where it should have checkbox like controls to mark the two that get the proficiency bonus and fields to enter the value. I also started with intent to have a bonus section for special bonuses to saves like from magic items or feats, and to list the source of bonus, but did not expose that part of each saving throw model right away.

This is where I hit the wall. In Elm you can reach down multiple levels by dot notation in getters but not in setters.  So I couldn't say what I initially tried, { model | saves.strength.proficient = not model.saves.strength.proficient } as the update response to clicking the checkbox for the strength saving throw proficiency. Stared at that awhile and started googling for what to do about it.

From what I see, you end up writing a custom setter function that reaches down and updates the innermost record you are updating an attribute on, and the one that encloses that, etc, all the way out. These can be kind of long, so you probably want to tuck them in a module devoted to a related collection of them There is a Focus module from Evan that can simplify this process some, with caveats about performance. There is a blog post about it by Wouter In t Velt that explains how to make some helper functions to shorten it.

But all options are enough of a pain that I didn't move on from there last night, and am considering flattening out the saving throws data structure and possibly doing the calculable dependent values based on character class selection, character level (and thus proficency bonus), and primary attribute modifier, and only making editable fields for special bonuses. The downside is less flexibility for additional character classess. I'll also start looking deeper into the ToDoMVC example to see how to update nested elements that are members of variable length lists like spells and weapon stat blocks.

The take home as a beginner one day into programming in Elm is that Elm is very pleasant and productive to work in if you understand the basics, with a stumbling block once you have to work with nested data models. I expect that is just the first place where I'll be paying back for the benefits of immutability and functional programming. So far though, it feels like a good trade. I want to get back in there and work on the next part.