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:
- 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.
- 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 installa package.
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 =
case validUser of
ValidData u -> u
-- Now we have a guaranteed valid user record