rethink frontend development with elm
TRANSCRIPT
Elm is a functional programming language like Haskell, but more friendly, and aimed at front-end web development.
We use Elm to make our user interface and give it behavior.
Elm compiles to JavaScript
Yes. We just wrote a bunch of code that gets injected into an HTML page.
Feel gross yet?
That's what React does too.
var HelloMessage = React.createClass({ render: function () { return <h1>Hello {this.props.message}!</h1>; }});
React.render(<HelloMessage message="World" />, document.body);
What you need
• Node.js http://nodejs.org
• The elm package for Node
$ npm install -g elm
• Your favorite text editor
ORhttp://elm-lang.org/try
Compiling Elm
• Create a hello.elm file• Run
$ elm make hello.elm Success! Compiled 1 modules. Successfully generated index.html
• Open resulting index.html in your browser.
Elm Reactor
Elm Reactor compiles Elm to HTML on each request.
$ elm-reactorelm reactor 0.16.0Listening on http://0.0.0.0:8000/
Functions
We define functions with a name followed by an = sign.
hello = "Hello there"
We indent the definitions of functions.
We invoke this function like this:
hello
Arguments
Functions can have arguments
square number = number * number
Call it as
square 2
They have explicit returns.
Multiple Arguments
Multiple arguments use spaces:
add number1 number2 = number1 + number2
Call it as
add 1 2
Woah.... no commas!
Type annotations
We can enforce data types for our functions so Elm can help us out.
functionName: TypeOfArg1-> TypeOfArg2 -> TypeOfArg3 -> ReturnType
Annotation Examples:
No parameters. Just return value
hello: Stringhello = "Hello there"
Two parameters and a return value
add: Float -> Float -> Floatadd number1 number2 = number1 + number2
Html functions
The elm-html module exposes many functions for building up virtual DOM nodes.
The main function can render HTML if the HTML module is included.
import Html exposing(p, text)
main = p [] [text "Hello World"]
p and text
p [] [text "Hello World"]
p and text are two functions from elm-html
p takes two lists
• a list of attributes (can be empty)• a list of child elements
text takes a string of text to display.
HTML functions are uniform.
Each takes attributes and elements. So we can nest them like HTML.
div [class "foo", id "bar" ] [ h1 [] [text "Hello"], p [] [text "World"]]
There's a function for every element. Just be sure to expose what you use.
Seriously uniform
label [for "name"] [text "Name"]input [id "name", type' "number", step "any"] []
Even functions for tags that don't allow inner content still take two lists as arguments.
Html Modules
• Html contains all the tags
• Html.Attributes contains the attributes (like class, id, href, etc)
• Html.Events contains events like onClick
Html Attributes
import Html exposing(Html, div, text, p)import Html.Attributes exposing(class)
main = div [class "wrapper"] [ p [class "notice"] [text "This is important!"] ]
Resuabilitymain = div [] [ view "Hello", view "Goodbye" ]
view: String -> Htmlview word = div [] [ p [] [ text (word ++ " "), em [] [text "world"] ] ]
Web Interfacesimport Html exposing(Html, Attribute, p, text)import Html.Attributes exposing(style)
elementStyle: AttributeelementStyle = style [ ("color", "red") , ("font-size", "2em") ]
main: Htmlmain = view
view = p [elementStyle] [text "Hello World"]
Helpers!
fieldWithLabel: String -> String -> String -> HtmlfieldWithLabel fieldID fieldName fieldType = div [] [ label [for fieldID] [text fieldName], input [ id fieldID, type' fieldType] [] ]
Build Out The Helpers
numberField: String -> String -> HtmlnumberField fieldID fieldName = fieldWithLabel fieldID fieldName "number"
textField: String -> String -> HtmltextField fieldID fieldName = fieldWithLabel fieldID fieldName "text"
emailField: String -> String -> HtmlemailField fieldID fieldName = fieldWithLabel fieldID fieldName "email"
Shiny Happy Frontend Code
main: Htmlmain = div [] [ textField "name" "Name", numberField "age" "Age", emailField "email" "Email" ]
Elm ArchitectureView: Function that fires when model changes. Transofms a model into the UI that people see.
Model: Something that holds the current state of the app. No behavior. Just the state. No behavior. This is not MVC with objects!
Update: Function that fires when state changes. Always returns a new model.
Signals and MailboxesSignalsSignals route messages around the application. Pressing a button is a signal. We can send data along signals.
MailboxesMailboxes receive signals and send signals. A mailbox has an address and a signal to respond to.
Basic Flow
• Model is initialized• View is displayed with model• Events send Signals to Mailboxes• Mailboxes trigger updates• New model is created• New view is rendered
Yikes!
Elm StartApp.Simple
Like Flux, without all the code.
• Define Actions
• Define a model to represent data
• Define a view function
• Define an update function that returns a new model.
Change Text On Clickimport Html exposing (Html, text, h1, p, div, button)import StartApp.Simple as StartAppimport Html.Events exposing (onClick)
main = StartApp.start {model = "Hello ", view = view, update = update}
view address initialText = div [] [ h1 [] [text "Events"], p [] [ text initialText ], button [onClick address "change"] [text "Push me"] ]
update action model = "it changed"
Actions
Actions get sent to the Update.
type Action = Increment | Decrement
model = 0
update: Signal.Action -> Int -> Intupdate action model = case action of Increment -> model + 1 Decrement -> model - 1
Multiple events
main = StartApp.start { model = model, view = view, update = update }
view: Signal.Address Action -> Int -> Htmlview address model = div [] [ button [ onClick address Increment ] [ text "Up" ] , span [] [ text (toString model) ] , button [ onClick address Decrement ] [ text "Down" ] ]
Once again...
• StartApp renders the view using an initial model state.
• Events defined in the view send Actions to Signal Addresses which route to update.
• update returns a new version of the model
• StartApp causes the view to be rendered whenever model changes.
Calculator
Compound Interest Calculator
Write a program to compute the value of an investment compounded over time. The program should ask for the starting amount, the number of years to invest, the interest rate, and the number of periods per year to compound.
Project setup
Create folder and file to work in
$ mkdir calculator && cd calculator$ touch calculator.elm
Init the project
$ elm package install
Install HTML and StartApp dependencies.
$ elm package install evancz/elm-html$ elm package install evancz/start-app
Steps
• Create the basic app• Build the form• Bind form to model and define events• Perform calculations• Display Output
The Basic Appimport Html exposing (Html, text, h1, p, div, button, label, input)import Html.Attributes exposing ( style, for, id, step, type', value)import StartApp.Simple as StartAppimport Html.Events exposing (onClick)
main = StartApp.start {model = model, view = view, update = update}
Define a model and update
model: Floatmodel = 0
update: String -> Float -> Floatupdate action model = model
Building the form
• Use label, input functions
• Use number fields• Each field change updates model state• Clicking button calculates new amount
numberField helper
numberField: String -> String -> HtmlnumberField fieldID fieldName = div [] [ label [for fieldID] [text fieldName], input [ id fieldID, type' "number", step "any"] [] ]
Style the form
labelStyle: AttributelabelStyle = style [ ("width", "200px") , ("padding", "10px") , ("text-align", "right") , ("display", "inline-block") ]
Apply style to field
div [] [ label [labelStyle, for fieldID] [text fieldName], input [ id fieldID, type' "number", step "any"] [] ]
Build the Viewview: Signal.Address String -> Float -> Htmlview address model = div [] [ h1 [] [text "Calculator"], div [] [ numberField "principal" "Principal", numberField "rate" "Rate", numberField "years" "Periods", numberField "years" "Years" ] button [onClick address "calculate"] [text "Calculate"] ]
Define Our Actions
type Action = NoOp | SetPrinciple String | SetPeriods String | SetRate String | SetYears String | Calculate
Define A Modeltype alias Model = { principle: String , rate: String , years: String , periods: String , newAmount: Float}
model: Modelmodel = { principle = "1500.00" , rate = "4.3" , years = "6" , periods = "4" , newAmount = 0 }
Pass address, action, and model data to fieldsview: Signal.Address Action -> Model -> Htmlview address model = div [] [ h1 [] [text "Calculator"], div [] [ numberField address SetPrinciple "principle" "Principle" model.principle, numberField address SetRate "rate" "Rate" model.rate, numberField address SetPeriods "periods" "Periods" model.periods, numberField address SetYears "years" "Years" model.years ], button [onClick address Calculate] [text "Click me"],
Add Events To Form using Actions and model datanumberField: Signal.Address Action -> (String -> Action) -> String -> String -> String -> HtmlnumberField address action fieldID name fieldValue = div [] [ label [labelStyle, for fieldID] [text name], input [id fieldID, type' "number", step "any", on "input" targetValue (Signal.message address << action ), value fieldValue] [] ]
Update model from formupdate: Action -> Model -> Modelupdate action model = case action of NoOp -> model
SetPrinciple p -> {model | principle = p}
SetRate r -> {model | rate = r}
SetYears y -> {model | years = y}
SetPeriods p -> {model | periods = p}
Calculate -> calculateNewAmount model
The program Logic
compoundInterest: Float -> Float -> Float -> Float -> FloatcompoundInterest principle rate periods years = (principle * (1 + (rate / periods ) ) ^ (years * periods) )
Converting Strings To Floats
convertToFloat: String -> FloatconvertToFloat string = case String.toFloat string of Ok n -> n Err _ -> 0.0
Implement CalculateNewAmountcalculateNewAmount: Model -> ModelcalculateNewAmount model = let rate = convertToFloat model.rate / 100 years = convertToFloat model.years principle = convertToFloat model.principle periods = convertToFloat model.periods in {model | newAmount = (compoundInterest principle rate periods years) }
Display the Output
output: Model -> Htmloutput model = div [] [ span [] [text "Amount: "], span [] [text (toString model.newAmount) ] ]
And add it to the view.
Discuss
What are your thoughts?
Is this cool? Good? Bad? A terrible idea or the greatest thing ever?
Issues
1. Tons of code to do simple things2. Integration with external services is
complex3. Must re-learn a lot of things about web
development4. Small community
Benefits
1. Small community2. Benefits of React with a clear
opinionated approach3. Fantastic error messages4. Types ensure data integrity and flow
Write code
• Elm website: http://elm-lang.org/• Try Elm http://elm-lang.org/try• Package system: http://package.elm-
lang.org/• Documentation http://elm-lang.org/docs
Where to go next?
Book: http://pragprog.com/titles/bhwb
Twitter: @bphogan
Material: http://bphogan.com/presentations/elm2016/
Thank you!© Brian Hogan, 2016. Photos from http://pexels.com