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