Building a web app

in the           ecosystem

 

runtime-exception-free guaranteed

Tessa Kelly

@t_kelly9

Engineer at NoRedInk

Elm

(almost)

Introduction

Who's the who

What's the what

Tessa Kelly - @t_kelly9 - NoRedInk

Following along

bit.ly/2pv7P2i

slides.com/tessak/elm-tutorial/live

Tessa Kelly - @t_kelly9 - NoRedInk

Helping teachers

teach grammar and writing

... using Elm

What have we built?

Some cool stuff.

Elm
Tools
Ecosystem

Tessa Kelly - @t_kelly9 - NoRedInk

Elm

An Introduction

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
view : Model -> Html Msg
view model =
    div [] [ text "Hello, world!" ]


type alias Model =
    {}


model : Model
model =
    {}


type Msg
    = NoOp


update : Msg -> Model -> Model
update msg model =
    case msg of
        NoOp ->
            model

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
// Relying on side-effects:
var counter = 0;
function incrementCounter () {
    counter++
}


// Limiting side-effects:
function incrementCounter (counter) {
    return ++counter
}

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
// incrementCounter : number -> number
function incrementCounter (counter) {
    return ++counter
}

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
// incrementCounter : number -> number
function incrementCounter (counter) {
    if (typeof counter === 'number') {
        return ++counter
    } else {
        throw "type error: called incrementCounter with non-number argument."
    }
}

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
// Polyfill for IE and Opera Mobile from MDN Number.isInteger entry(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger)
Number.isInteger = Number.isInteger || function(value) {
  return typeof value === 'number' &&
    isFinite(value) &&
    Math.floor(value) === value;
};

function incrementCounter (counter) {
    if (Number.isInteger(counter)) {
        return ++counter
    } else {
        throw "type error: called incrementCounter with non-integer argument."
    }
}

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
incrementCounter : Int -> Int
incrementCounter counter =
    counter + 1

Tessa Kelly - @t_kelly9 - NoRedInk

Let's just side by side that...

// Polyfill for IE and Opera Mobile from MDN Number.isInteger entry (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger)
Number.isInteger = Number.isInteger || function(value) {
  return typeof value === 'number' &&
    isFinite(value) &&
    Math.floor(value) === value;
};

function incrementCounter (counter) {
    if (Number.isInteger(counter)) {
        return ++counter
    } else {
        throw "type error: called incrementCounter with non-integer argument."
    }
}
incrementCounter : Int -> Int
incrementCounter counter =
    counter + 1

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
The argument to function incrementCounter is causing a mismatch.
Function incrementCounter is expecting the argument to be:

Int

But it is:

Float

Hint: Elm does not automatically convert between Ints and Floats. 
Use toFloat and round to do specific conversions.

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
-- error: TYPE MISMATCH - /Users/tessakelly/Documents/elm-piano/src/Model.elm:18:1
The definition of `init` does not match its type annotation.
The type annotation for `init` says it always returns:
    Model
But the returned value (shown above) is a:
    { currentlyPlaying : AllDict.AllDict ( Octave, Note ) v Float
    , debounce : Maybe a
    , octave : number
    , played : List b
    , time : number1
    }
Hint: The record fields do not match up. Maybe you made one of these typos?
    
    debouncer <-> debounce

Tessa Kelly - @t_kelly9 - NoRedInk

Why do people like Elm?

  • Functional style
  • Type safety
  • The compiler
ourFloat : Float
ourFloat =
    1.000000


counter : Int
counter =
    round ourFloat


incrementedCounter : Int
incrementedCounter =
    incrementCounter counter
{- MORE SUCCINCTLY -}
incrementedCounter : Int
incrementedCounter =
    incrementCounter 
        (round 1.000000)

Tessa Kelly - @t_kelly9 - NoRedInk

Any questions?
 

Tessa Kelly - @t_kelly9 - NoRedInk

Getting Started

Fingers to Keyboard

Tessa Kelly - @t_kelly9 - NoRedInk

ELm

LIve

Editor

Tessa Kelly - @t_kelly9 - NoRedInk

Hello, World

https://ellie-app.com/new

Tessa Kelly - @t_kelly9 - NoRedInk

Prairie Chicken, Lesser

An Accounting

Tessa Kelly - @t_kelly9 - NoRedInk

Adding View Arguments

https://ellie-app.com/38fqWH7K6xYa1/0

Tessa Kelly - @t_kelly9 - NoRedInk

Adding a Count Button

https://ellie-app.com/38frj3F28mYa1/0

Tessa Kelly - @t_kelly9 - NoRedInk

The Elm Architecture

Tessa Kelly - @t_kelly9 - NoRedInk

The Elm Architecture

main =
    beginnerProgram
        { model = model 
            -- our application state and data
        , update = update  
            -- the way that the application 
            -- state and data can change
        , view = view -- the view!
        }

Tessa Kelly - @t_kelly9 - NoRedInk

Wiring it all up!

(part 1)

https://ellie-app.com/38frCLYqNHza1/0

Tessa Kelly - @t_kelly9 - NoRedInk

Wiring it all up!

(part 2)

https://ellie-app.com/38frV3W7KLHa1/0

Tessa Kelly - @t_kelly9 - NoRedInk

Questions?

With notes: https://ellie-app.com/38fsmjLscCza1/0

Without: https://ellie-app.com/38fsNbsDtQza1/0

Tessa Kelly - @t_kelly9 - NoRedInk

Exercise Interlude

Killing Chickens

and

Conditional Logic

Tessa Kelly - @t_kelly9 - NoRedInk

Decreasing Chickens

EXERCISE

Add decrement functionality. (Sad lesser prairie chicken!)

SOLUTION

https://ellie-app.com/38ftQWKynkxa1/0

Tessa Kelly - @t_kelly9 - NoRedInk

There are: 1 chickens

Conditional Logic

formatChickenCount : Int -> String
formatChickenCount count =
    if count == 1 then
        "There is 1 chicken."
    else
        "There are " ++ toString count ++ " chickens."

Tessa Kelly - @t_kelly9 - NoRedInk

Plural Agreement

EXERCISE

Add the formatChickenCount function to the application.

SOLUTION

https://ellie-app.com/38fvtpY288xa1/0

formatChickenCount : Int -> String
formatChickenCount count =
    if count == 1 then
        "There is 1 chicken."
    else
        "There are " ++ toString count ++ " chickens."

Tessa Kelly - @t_kelly9 - NoRedInk

Negative Chickens

EXERCISE

Prevent users from decrementing more chickens than is possible.

SOLUTION

https://ellie-app.com/38fvQ3T3DN7a1/0

Tessa Kelly - @t_kelly9 - NoRedInk

Elm Reactor

Running Code Locally

Tessa Kelly - @t_kelly9 - NoRedInk

Running Code Locally

$ elm

Elm Platform 0.18.0 - a way to run all Elm tools

Usage: elm <command> [<args>]

Available commands include:

  make      Compile an Elm file or project into JS or HTML
  package   Manage packages from <http://package.elm-lang.org>
  reactor   Develop with compile-on-refresh and time-travel debugging
  repl      A REPL for running individual expressions

You can learn more about a specific command by running things like:

  elm make --help
  elm package --help
  elm <command> --help

In all these cases we are simply running 'elm-<command>' so if you create an
executable named 'elm-foobar' you will be able to run it as 'elm foobar' as
long as it appears on your PATH.
  • Checking our install
  • Setting up our project
  • Starting elm-reactor

Tessa Kelly - @t_kelly9 - NoRedInk

Running Code Locally

$ mkdir prairie-chicken-counter
$ cd prairie-chicken-counter
$ touch Main.elm
  • Checking our install
  • Setting up our project
  • Starting elm-reactor

Tessa Kelly - @t_kelly9 - NoRedInk

Running Code Locally

$ elm reactor

elm-reactor 0.18.0
Listening on http://localhost:8000
  • Checking our install
  • Setting up our project
  • Starting elm-reactor

Tessa Kelly - @t_kelly9 - NoRedInk

Add.

Save.

Refresh.

Tessa Kelly - @t_kelly9 - NoRedInk

Syntactical

A Quick Syntax Primer

Tessa Kelly - @t_kelly9 - NoRedInk

Strings

{-| Using (++) as an infix operator:
-}
exclaim : String -> String
exclaim comment =
    comment ++ "!"


{-| Using (++) as a prefix operator:
-}
noSeriouslyExclaimTho : String -> String
noSeriouslyExclaimTho comment =
    (++) comment "!!!!"


{- More complex operations with Strings can be explored 
in the String documentation:
http://package.elm-lang.org/packages/elm-lang/core/5.1.1/String
-}

Tessa Kelly - @t_kelly9 - NoRedInk

Numbers

{-| Example of a float.
-}
float : Float
float =
    99.99


{-| Example of an integer.
-}
integer : Int
integer =
    round float


{- Check out the Basics docs for more: 
http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Basics
-}

Tessa Kelly - @t_kelly9 - NoRedInk

Lists

anEmptyList : List a
anEmptyList =
    []


aListOfStrings : List String
aListOfStrings =
    [ "Just", "some", "strings!" ]


hasMyName : List String -> Bool
hasMyName nameList =
    List.member "Tessa" nameList


addAnElementToTheFrontOfTheList : List String
addAnElementToTheFrontOfTheList =
    "cons" :: [ "is the name of this operator" ]

{- Lists also have package documentation in the 
core section of the package website.

http://package.elm-lang.org/packages/elm-lang/core/5.1.1/List
-}

Tessa Kelly - @t_kelly9 - NoRedInk

Records

someRecord =
    { whatIsIt = "Named key value pairs"
    , doTheyAllNeedTheSameType = False
    , howDoesThisWorkInTheTypeSystem =
        "We'll look at type aliases on the next slide"
    , someValue = ( 1, 2, 3 )
    }


updatedRecord =
    { someRecord
        | whatIsIt =
            "An updated record--the old record isn't changed by this operation"
    }


getSomeValue : { a | someValue : b } -> b
getSomeValue argument =
    argument.someValue

getSomeValueTwo : { a | someValue : b } -> b
getSomeValueTwo argument =
    .someValue argument

getSomeValueThree : { a | someValue : b } -> b
getSomeValueThree { someValue } =
    someValue

Tessa Kelly - @t_kelly9 - NoRedInk

Type Aliases

{-| Type aliases are a convenience that will help the
compiler give you nice error messages.
-}
type alias RecordExample =
    { fieldName : String
    , aBoolField : Bool
    , somethingElse : List ( String, Bool ) -- This is a list of tuples.
    }


aRecord : RecordExample
aRecord =
    { fieldName = "field value"
    , aBoolField = False
    , somethingElse = [ ("Hello", True) ] 
    }


{-| Type aliases give you an order-based constructor. -}
anotherRecord : RecordExample
anotherRecord =
    RecordExample
        """ fieldName values are strings, so we need to pass in a string first.
            Use triple double-quotation marks for a multi-line string!
        """
        True
        []



Tessa Kelly - @t_kelly9 - NoRedInk

Functions

iAmAFunction : String -> String
iAmAFunction argument =
    "This is the body of the function, plus the argument: " ++ argument


iAmAFunctionWithLocalScope : Int -> Int
iAmAFunctionWithLocalScope argument =
    let
        iAmALocalVariable : Int
        iAmALocalVariable =
            7

        localFunction localArgument =
            9 + localArgument
    in
        argument + localFunction 10


{-| `\item -> item ++ "!!!"` is an anonymous function.
-}
exclaimList : List String -> List String
exclaimList =
    -- Notice  that we haven't explicitly named the argument to this function
    List.map (\item -> item ++ "!!!")

Tessa Kelly - @t_kelly9 - NoRedInk

Special Operators: Pipe

{- `(|>)`, `(<|)`

These operators pass the result of an operation on. 
Pipes make it easier to do many data transformations in
a row and can serve as a replacement for parentheses.
-}


{-| This is "Hello!!!" -}
helloWithParens : String
helloWithParens =
    ((++) "Hello") "!!!"


{-| This is also "Hello!!!", but notice the `|>` instead of the parens -}
helloWithPipeOperator : String
helloWithPipeOperator =
    "!!!" |> (++) "Hello"

Tessa Kelly - @t_kelly9 - NoRedInk

Special Operators: Compose

{- `(>>)`, `(<<)` -- compose functions.
-}


mathItUpWithoutFunctionComposition : List Int -> List Int
mathItUpWithoutFunctionComposition =
    List.map (\value -> value * 2 + 1)


{-| mathItUp [ 0, 1, 2  ] == [ 1, 3, 5 ]
-}
mathItUp : List Int -> List Int
mathItUp =
    List.map ((*) 2 >> (+) 1)



Tessa Kelly - @t_kelly9 - NoRedInk

Elm ✓
Tools
Ecosystem

Tessa Kelly - @t_kelly9 - NoRedInk

Tools

A Brief Overview

Tessa Kelly - @t_kelly9 - NoRedInk

Tessa Kelly - @t_kelly9 - NoRedInk

Tools to know and love

  • ellie
  • elm-format
  • elm-oracle
  • create-elm-app
  • elm-doc-test
  • elm-test
  • debugger
  • elm-html-a11y
  • elm-decode-pipeline
  • Elm Search
  • ... and many more!

Adding elm-format to your editor:

Tessa Kelly - @t_kelly9 - NoRedInk

https://github.com/avh4/elm-format

view : Model -> Html Msg
view model = div [class[ Container ]
        ] [Html.CssHelpers.style css,
 viewPiano model.currentlyPlaying model.octave, label [for "input" ] [text "Play input" ], input [autofocus True , onKeyDown (Update.Debounce << Update.HandleKeyDown) , onKeyUp (Update.withNote (\note -> Update.Stop ( model.octave, note ))), 
            id "input"][] , br[][] , h4[] [ text "Record:" ]
        , viewPlayedNotes model.played , h4[] [ text "Playing:" ]
        , viewPlayingNotes model.currentlyPlaying 
]
viewPiano : AllDict.AllDict ( Model.Octave, Model.Note ) a Float -> Model.Octave -> Html Msg
viewPiano currentlyPlaying selectedOctave =
 div [class[Piano]] (List.repeat 7 () |> List.indexedMap (\index _ -> viewOctave currentlyPlaying selectedOctave index))
view : Model -> Html Msg
view model =
    div
        [ class [ Container ]
        ]
        [ Html.CssHelpers.style css
        , viewPiano model.currentlyPlaying model.octave
        , label [ for "input" ] [ text "Play input" ]
        , input
            [ autofocus True
            , onKeyDown (Update.Debounce << Update.HandleKeyDown)
            , onKeyUp (Update.withNote (\note -> Update.Stop ( model.octave, note )))
            , id "input"
            ]
            []
        , br [] []
        , h4 [] [ text "Record:" ]
        , viewPlayedNotes model.played
        , h4 [] [ text "Playing:" ]
        , viewPlayingNotes model.currentlyPlaying
        ]


viewPiano : AllDict.AllDict ( Model.Octave, Model.Note ) a Float -> Model.Octave -> Html Msg
viewPiano currentlyPlaying selectedOctave =
    div [ class [ Piano ] ] (List.repeat 7 () |> List.indexedMap (\index _ -> viewOctave currentlyPlaying selectedOctave index))

The Debugger

EXERCISE

Use the debugger on Ellie to send a character back and forth through time, and find the bug...

https://ellie-app.com/37HyTh3GFRza1/4

  • We can step through the history to any point
  • We can import and export histories from our application

tesk9/elm-html-a11y

Elm ✓
Tools ✓
Ecosystem

Tessa Kelly - @t_kelly9 - NoRedInk

Packages

Getting Started

Tessa Kelly - @t_kelly9 - NoRedInk

The Package Site

http://package.elm-lang.org/

Tessa Kelly - @t_kelly9 - NoRedInk

Installing Packages

(Or--back to our lesser prairie chickens!)

$ elm-package install rtfeldman/elm-css

To install rtfeldman/elm-css I would like to add the following
dependency to elm-package.json:

    "rtfeldman/elm-css": "8.2.0 <= v < 9.0.0"

May I add that to elm-package.json for you? [Y/n] y

Some new packages are needed. Here is the upgrade plan.

  Install:
    elm-lang/core 5.1.1
    elm-lang/html 2.0.0
    elm-lang/virtual-dom 2.0.4
    rtfeldman/elm-css 8.2.0
    rtfeldman/elm-css-util 1.0.2
    rtfeldman/hex 1.0.0

Do you approve of this plan? [Y/n] y

So what do we have?

  • elm-stuff
  • elm-package.json
{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "."
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-lang/core": "5.1.1 <= v < 6.0.0",
        "elm-lang/html": "2.0.0 <= v < 3.0.0",
        "rtfeldman/elm-css": "8.2.0 <= v < 9.0.0"
    },
    "elm-version": "0.18.0 <= v < 0.19.0"
}

Tessa Kelly - @t_kelly9 - NoRedInk

elm-package.json for elm-css

{
    "version": "8.2.0",
    "summary": "CSS Preprocessor for Elm",
    "repository": "https://github.com/rtfeldman/elm-css.git",
    "license": "BSD-3-Clause",
    "source-directories": [
        "src"
    ],
    "exposed-modules": [
        "Css",
        "Css.Colors",
        "Css.Elements",
        "Css.File",
        "Css.Namespace"
    ],
    "dependencies": {
        "elm-lang/core": "5.0.0 <= v < 6.0.0",
        "rtfeldman/elm-css-util": "1.0.2 <= v < 2.0.0",
        "rtfeldman/hex": "1.0.0 <= v < 2.0.0"
    },
    "elm-version": "0.18.0 <= v < 0.19.0"
}

Tessa Kelly - @t_kelly9 - NoRedInk

Using the package

import Css exposing (asPairs, color)
import Css.Colors exposing (green)
import Html.Attributes exposing (style)
import Html exposing (..)
import Html.Events exposing (onClick)

...

view : Int -> Html Msg
view count =
    div [ (style << asPairs) [ color green ] ]
        [ h1 [] [ text "Prairie Chicken, Lesser" ]
        , h2 [] [ text "An Accounting of Hens" ]
        , div []
            [ text (formatChickenCount count)
            ]
        , viewIncrementButton
        , if count > 0 then
            viewDecrementButton
          else
            text ""
        ]

Tessa Kelly - @t_kelly9 - NoRedInk

View customization

EXERCISE

Style the application to your specifications.

SOLUTION

No solution here--but save questions for me. :)

What's good?

Tessa Kelly - @t_kelly9 - NoRedInk

  • Markdown-based documentation
  • Enforced semantic versioning
  • Required documentation

What's good?

Tessa Kelly - @t_kelly9 - NoRedInk

  • Markdown-based documentation
  • Required documentation
  • Enforced semantic versioning

Let's add docs.

EXERCISE

Add doc comments to your application. See how your comments look via:

package.elm-lang.org/help/docs-preview.

SOLUTION

We'll walk through this together.

What's good?

Tessa Kelly - @t_kelly9 - NoRedInk

  • Markdown-based documentation
  • Required documentation
  • Enforced semantic versioning

Any questions?
 

Tessa Kelly - @t_kelly9 - NoRedInk

Going Further

Additional Exercises

Tessa Kelly - @t_kelly9 - NoRedInk

Adding a Model

EXERCISE

Add and use a Model type alias. The Model is generally a record: make the chicken count a field on the model.

SOLUTION

https://ellie-app.com/38fvQ3T3DN7a1/1

Keeping it going...

EXERCISE

Switch from `beginnerProgram` to `program`.

SOLUTION

We'll need to make some pretty substantial changes.

https://ellie-app.com/38fvQ3T3DN7a1/2

-- HINT:

main =
    program
        { init = init
        , update = update
        , view = view
        , subscriptions = 
            \model -> Sub.none
        }

Embedding

EXERCISE

Switch from Fullscreen to embedding here:

This enables us to use a single DOM node rather than the full screen.

SOLUTION

var node = document.getElementById("some-sweet-id")
var app = Elm.Main.embed(node)

Towards JavaScript

EXERCISE

Use `elm-make Main.elm --output=main.js` to produce your elm code (instead of elm-reactor). Use a normal .html file and script tag to load.

SOLUTION

We'll walk through this together!

Towards JavaScript

EXERCISE

Console.log a message whenever a user decrements a chicken.

(You'll need to read about Ports!)

SOLUTION

We'll walk through this together!

Going Further

Resources and Community

Tessa Kelly - @t_kelly9 - NoRedInk

Free Resources

  • The Guide: https://guide.elm-lang.org/
  • ElmBridge: https://elmbridge.github.io/curriculum/
  • Blog posts compilation: http://www.elmweekly.nl/

Tessa Kelly - @t_kelly9 - NoRedInk

Tessa Kelly - @t_kelly9 - NoRedInk

Community

  • Slack: elmlang.slack.com
  • Reddit: https://www.reddit.com/r/elm/
  • Google group: https://groups.google.com/forum/#!forum/elm-discuss
  • ... and for everything: http://elm-lang.org/community

Breaking Things

About those runtime exceptions...

Tessa Kelly - @t_kelly9 - NoRedInk

The Debug module!

EXERCISE

Use `Debug.crash` to crash your program.

SOLUTION

main =
    text "Hello, World!"
    |> \_ -> Debug.crash "Goodbye, World!"

Tessa Kelly - @t_kelly9 - NoRedInk

Bad Recursion

even : Int -> Bool
even n =
  case n of
    0 ->
      True
    _ ->
      odd (n - 1)

odd : Int -> Bool
odd n =
  case n of
    0 ->
      False
    _ ->
      even (n - 1)

Example from joneshf/elm-tail-recursion

(Check out the package docs to learn how to avoid this!)

EXERCISE

See if you can recurse your way to an exception.

Tessa Kelly - @t_kelly9 - NoRedInk

Any questions?
 

Tessa Kelly - @t_kelly9 - NoRedInk

Thank you!

:tree:

Tessa Kelly - @t_kelly9 - NoRedInk

Elm Tutorial

By Tessa K

Elm Tutorial

Introduction to the Elm programming language.

  • 1,325