Fearless refactoring
with elm
by Daniel Bachler (@danyx23)
this presentation is available at:
slides.com/danielbachler/fearless-refactoring-with-elm
25 minutes
the language
the architecture
the infrastructure
my experience so far
my pain points with js
- No static typing means simple typos can lead to runtime errors
- Deep changes to data structures in non-trivial JS projects need increadible amounts of diligence and attention
- A big part of the unit & integration tests test really boring properties (does it correctly reject an undefined param, ...)
What is elm?
- a functional programming language with static type checking that is compiled to JS
- an FRP (functional reactive programming) runtime
- designed to be easy to learn and good for web frontend dev
- uses VDOM to efficiently update the DOM, similar to React
Elm, the language
- Pure functional
- Statically typed
- Beginner friendly
function addNumbers(a, b) {
return a + b;
}
// Whatever
addNumbers(4, "three");
addNumbers a b =
a + b
-- Types are infered to be numbers
-- This is a compile time error
addNumbers 4 "three"
super friendly and helpful compiler
some personal favourite features
Union types
-- Union Type
type Gender
= Male
| Female
| Other String
personAGender = Male
personBGender = Other "Androgynous"
printGender gender =
case gender of
Male ->
"male"
Female ->
"female"
Other selfdefined ->
selfdefined
Non-nullable values
-- There is no null, no undefined
type alias Person =
{ firstName : String
, lastName : String
, yearOfBirth : Int
}
-- not possible
personA = { firstName = "Lao"
, lastName = "Tse"
, yearOfBirth = null
}
birthYearToString person =
toString person.yearOfBirth
-- instead:
type alias Person =
{ firstName : String
, lastName : String
, yearOfBirth : Maybe Int
}
personA = { firstName = "Lao"
, lastName = "Tse"
, yearOfBirth = Nothing
}
birthYearToString person =
case person.yearOfBirth of
Nothing ->
"Unknown"
Just age ->
toString age
all values are immutable
x = 1
x = 2 -- compile error
x = x + 1 -- compile error
y = x + 1 -- ok
static typechecking
addNumbers : Int -> Int -> Int
addNumbers a b =
a + b
birthYearToString : Person -> String
birthYearToString person =
...
- The compiler always knows if your types check out or not
- You can add Type Annotations so the compiler can tell you where you went wrong
- Elm is always strongly typed, whether you add annotations or not (unlike Typescript, Flow, ...)
- There is no "any" type like in TypeScript
All functions are pure
addNumbers : Int -> Int -> Int
addNumbers a b =
a + b
result1 = addNumbers 1 2
result2 = addNumbers 1 2
result 1 == result 2 -- True
Calling the same function again with the same arguments will always return the same value, never have any side effects
function addNumbersWeird(a, b) {
window.myGlobalState = window.myGlobalState || 0;
return a + b + (window.myGlobalState++);
}
var result1 = addNumbersWeird(1, 2);
var result2 = addNumbersWeird(1, 2);
result1 == result2; // False
fearless refactoring
- Compiler nags until all refactoring bugs are fixed
- Say goodbye to "undefined is not a function"
- No runtime errors in Elm
Unit tests can focus on logic
- In Javascript, unit tests have to check for undefined etc
- In Elm, the type system takes care of all of this
- Unit tests in Elm can focus on behaviour and logic
- Pure functions are super nice to test (little mocking required)
The Elm Architecture
Very similar to React without local state + Redux
Diagram by Evan Czaplicki
The Elm Architecture
See also André Staltz' great overview of unidirectional UI aproaches
The Elm Architecture
-- MODEL
type alias Model = Int
-- UPDATE
type Action = Increment | Decrement
update : Action -> Model -> Model
update action model =
case action of
Increment ->
model + 1
Decrement ->
model - 1
-- VIEW
view : Signal.Address Action -> Model -> Html
view address model =
div []
[ button [ onClick address Decrement ] [ text "-" ]
, div [ countStyle ] [ text (toString model) ]
, button [ onClick address Increment ] [ text "+" ]
]
countStyle : Attribute
countStyle =
style
[ ("font-size", "20px")
, ("font-family", "monospace")
, ("display", "inline-block")
, ("width", "50px")
, ("text-align", "center")
]
-- Wire everything together
main =
start
{ model = 0
, update = update
, view = view
}
benefits
- Model is single source of truth
- Creating visual elements is done by running the Model through a pure view function
- Side effects (XHRs, ...) are modelled as Tasks, handled by the runtime, results come back into the app as actions
- Apps are well structured. All state modifications happen in the central update (or a function called from there)
- Easy to replay whole UI sessions (example)
The infrastructure
elm make
elm repl
elm package
package manager
- Handles installation of packages
- And publishing (inkl updates)
- Automatically enforces SemVer (!!)
Interop with "native Javascript"
- Incoming Ports (send messages from JS -> Elm)
- Outgoing Ports (send messages from Elm -> JS)
- Native modules (modules that use native JS - need to pass native review to be whitelisted)
Personal experience
- Last spring, started work on a side project, a slideshow editor
- Chose React for rendering (only pure components) with Redux to manage state (single source of truth) + Immutable.js
- Many nice properties, but refactoring was still a pain. Also pretty verbose, immutable.js field access via strings, ...
- Evaluated Typescript, Flow
- Then took a closer look at elm and really liked it
+
- General experience was very pleasant
- Compiler is very helpful, many bugs can't even exist, code quality is higher
- Refactoring with joy and confidence -> makes you do it more -> makes the code better
- Community is friendly and helpful
- Elm is small enough to learn it quickly, but still a very good stepping stone to other functional languages, esp. Haskell
- Code reads well
-
- Some rough edges (e.g. I use a script that deletes the compiler intermediate files before every compile because it would occasionally miss changed files)
- Elm core team is small, some PRs to Docs etc linger for a long time (will hopefully get better with the new Elm Foundation)
- Almost no support for running outside the Browser (=no server side rendering, ...)
- Some trivial things in JS are a bit of a hassle in Elm, e.g. focusing a control (has to be done via port)
Would I do it again?
absolutely!
How to get started
- try.elm-lang.org and debug.elm-lang.org
- elm-lang.org - great docs, read "The Elm Architecture"
- Check out awesome-elm
- try it for one widget of your app or a small side project
- If you don't know any other functional languages, the first steps will be a bit rough ("What do you mean there are no loops?")
- But it is very beginner friendly and a small, well-designed language
- Used in production today by several companies
Should we do a hackathon style
meetup in Berlin?
Thank you!
by Daniel Bachler (@danyx23 on twitter)