I built a serverless (progressive) web app ppkk.fi using Elm. Elm is a functional language that compiles to JavaScript so you can make web apps and components to use on web sites. Elm is not an extension to JavaScript or something. Indeed, Elm is a stand-alone programming language with its own compiler and standard libraries.
Getting started wasn’t easy. Was the steep learning curve worth it? At this point I’d say it was. A quick list of first impression pros and cons:
Pros
- When it compiles, it works.
- Elm-UI makes it possible to avoid complex CSS.
- Forces good program structure.
- Pure functions and static types make runtime errors go away.
Cons
- S l o w to rapidly test new app features. Always have to tie up all loose ends.
- Have to build up from low level. In JS you’d just
npm install
a package.
Elm’s pure functions and immutable values forced me to focus on program states, and write functions that are very explicit about the state changes. It was difficult to get right in the beginning. After some UML state diagramming I got the first version running. I quickly realized my first state structure was lacking and started to refactor the code. That was definitely Elm’s strength: refactoring was not the nightmare it often is with e.g. JavaScript. Elm’s compiler checked everything for me, pointed out errors, and I could trust that whenever compiler errors were fixed, I had a pretty solid program up and running again.
For example, I originally had a user data record that was passed to functions building the user interface views. It worked but was difficult with side effects like save to database. So I tagged the data record with a custom type RemoteData that explicitly models writes and loads. With RemoteData User
it was nice to build a user interface that doesn’t leave the user wondering if something’s happening or not.
It was even nicer was to find out about phantom types. I could use a phantom type to restrict function parameters to e.g. only “write done” RemoteData User
. So now the compiler would check—in compile time—that a function is called with only Users whose data are safe in the database. Proper types and the compilers type checking would help me write an app that would have no runtime errors!
Conclusion: for a simple app Elm was a delightful experience. The result is fast and efficient. I have a feeling tha the code will be reasonable easy to maintain. Specifically, building the user interface with Elm-UI was great. I spent much less than usual time tweaking CSS.
-- 1st version, won't work with side effects
userUpdated : User.Model -> El.Element Msg
userUpdated user =
-- ...
-- 2nd version, with remote data
userUpdated : RemoteData User.Model -> El.Element Msg
userUpdated rUser =
case rUser of
Loading -> -- Show spinner
Stored -> -- Show the updated view
-- 3rd version with phantom types
type ValidData a = ValidData
type Loading = Loading
type Saved = Saved
userUpdated : ValidData User.Model -> El.Element Msg
userUpdated validUser =
user =
case validUser of
ValidData u -> u
-- Now we have a guaranteed valid user record