welcome to elm! - frontend masters · welcome to elm! please follow these ... let-expressions....

234
https://github.com/rtfeldman/elm-workshop welcome to elm! please follow these instructions to get set up:

Upload: dothuan

Post on 04-Jun-2018

216 views

Category:

Documents


0 download

TRANSCRIPT

https://github.com/rtfeldman/elm-workshop

welcome to elm!

please follow these instructions to get set up:

elm@rtfeldman

1. Rendering a Page

elmcompiles to

functions

arguments

no parentheses

required

expression

expression

JS: quantity === 1 ? singular : plural

call pluralize passing 3 arguments

call text passing 1 argument

import the Html module

why bother?

comments about comments

-- this is a comment

-- this is a comment

{- this is ablock comment -}

Html

Virtual DOM

<ul class="highway"> <li>danger</li> <li>zone</li></ul>

<ul class="highway"> <li>danger</li> <li>zone</li></ul>

ulclass="highway"

li li

text text“danger” “zone”

<ul class="highway"> <li>danger</li> <li>zone</li></ul>

ulclass="highway"

li li

text text“danger” “zone”

ul [ class "highway" ] [ li [] [ text "danger" ], li [] [ text "zone" ] ]

<ul class="highway"> <li>danger</li> <li>zone</li></ul>

ulclass="highway"

li li

text text“danger” “zone”

ul [ class "highway" ] [ li [] [ text "danger" ] , li [] [ text "zone" ] ]

Exercise: resolve the TODOs in part1/Main.elm

<ul class="highway"> <li>danger</li> <li>zone</li></ul>

ul [ class "highway" ] [ li [] [ text "danger" ] , li [] [ text "zone" ] ]

2. Basic Data Structures

strings

"foo" ++ "bar" == "foobar"

"foo" ++ "bar" == "foobar"

"foo" + "bar" === "foobar" // JS

toString 5 == "5"

let-expressions

pluralize singular plural quantity = if quantity == 1 then singular else plural

pluralize singular plural quantity = if quantity == 1 then toString quantity ++ " " ++ singular else toString quantity ++ " " ++ plural

pluralize singular plural quantity = if quantity == 1 then toString quantity ++ " " ++ singular else toString quantity ++ " " ++ plural

code duplication

pluralize singular plural quantity = let quantityStr = toString quantity

prefix = quantityStr ++ " " in if quantity == 1 then prefix ++ singular else prefix ++ plural

pluralize singular plural quantity = let quantityStr = toString quantity

prefix = quantityStr ++ " " in if quantity == 1 then prefix ++ singular else prefix ++ plural

let-expression

pluralize singular plural quantity = let quantityStr = toString quantity

prefix = quantityStr ++ " " in if quantity == 1 then prefix ++ singular else prefix ++ plural

inaccessible inoutside scope

pluralize singular plural quantity = let quantityStr = toString quantity

prefix = quantityStr ++ " " in if quantity == 1 then prefix ++ singular else prefix ++ plural

entire expressionevaluates to this

collections

Records, Tuples, and Lists

record = { name = "thing", x = 1, y = 3 }

record = { name = "thing", x = 1, y = 3 }

record.name == "thing"record.x == 1record.y == 3

record = { name = "thing", x = 1, y = 3 }

tuple = ( "thing", 1, 3 )

a tuple is shorthand for a record

record = { name = "thing", x = 1, y = 3 }

tuple = ( "thing", 1, 3 )

( name, x, y ) = ( "thing", 1, 3 )

tuple destructuring

record = { name = "thing", x = 1, y = 3 }

tuple = ( "thing", 1, 3 )

( name, x, y ) = ( "thing", 1, 3 )

name == "thing"x == 1

[ 1, 2, 3 ]

[ 1, 2, 3 ]

[ [ "foo", "bar" ], [ "baz" ] ]

[ 1, 2, 3 ]

[ [ "foo", "bar" ], [ "baz" ] ]

[ "foo", 66 ]

what does this rule get us?

Records Tuples Lists

Records Tuples Lists

fixedlength

fixed length

variable length

Records Tuples Lists

mixedcontents

mixedcontents

uniformcontents

fixedlength

fixed length

variable length

Exercise: resolve the TODOs in part2/Main.elm

record = { x = 4, y = 2 }

record.x == 4record.y == 2

toString 99 == "99"

negate (5 + 9) == -14 toString 99 ++ " balloons" == "99 balloons"

3. Adding Interaction

booleans

True

False

True

False

x == y

True

False

x == y

not (x == y)

True

False

x == y

not (x == y)

x /= y

partial application

function pluralizeLeaves(quantity) { return pluralize("leaf", "leaves", quantity);}

function pluralizeLeaves(quantity) { return pluralize("leaf", "leaves", quantity);}

pluralizeLeaves quantity = pluralize "leaf" "leaves" quantity

function pluralizeLeaves(quantity) { return pluralize("leaf", "leaves", quantity);}

pluralizeLeaves quantity = pluralize "leaf" "leaves" quantity

pluralizeLeaves = pluralize "leaf" "leaves"

function pluralizeLeaf(plural, quantity) { return pluralize("leaf", plural, quantity);}

pluralizeLeaf plural quantity = pluralize "leaf" plural quantity

pluralizeLeaf = pluralize "leaf"

List.filter

isKeepable num = num >= 2

List.filter isKeepable [ 1, 2, 3 ]

== [ 2, 3 ]

Elm: (\foo -> foo + 1)

JS: (function(foo) { return foo + 1 })

List.filter (\num -> num >= 2) [ 1, 2, 3 ]

== [ 2, 3 ]

isKeepable num = num >= 2

List.filter isKeepable [ 1, 2, 3 ]

== [ 2, 3 ]

List.map

double num = num * 2

double num = num * 2

List.map double [ 1, 2, 3 ]

double num = num * 2

List.map double [ 1, 2, 3 ]

== [ 2, 4, 6 ]

List.map negate [ 1, 2, -3 ]

== [ -1, -2, 3 ]

List.map (pluralize "leaf" "leaves") [ 1, 2, 3 ]

== [ "1 leaf", "2 leaves", "3 leaves" ]

The Elm Architecture

model view update

view model =

div [ class "content" ] []

view

Html

Elm Runtime

view

Htmlh1 [] [ text "ElmHub" ]

Elm Runtime

viewModel

Htmlh1 [] [ text "ElmHub" ]

Elm Runtime

viewModel

Htmlh1 [] [ text "ElmHub" ]

{ query = "tutorial"

, results = []

}

Elm Runtime

viewupdate Model

Htmlh1 [] [ text "ElmHub" ]

{ query = "tutorial"

, results = []

}

Elm Runtime

maxResults and Show More

potential feature:

{ operation = "SHOW_MORE", data = 10}

{ operation = "SHOW_MORE", data = 10}

update msg model = if msg.operation == "SHOW_MORE" then { maxResults = model.maxResults + msg.data } else model

update msg model = if msg.operation == "SHOW_MORE" then { maxResults = model.maxResults + msg.data } else model

{ operation = "SHOW_MORE", data = 10}

what if there are other fields in the model?

update msg model = if msg.operation == "SHOW_MORE" then { maxResults = model.maxResults + msg.data } else model

update msg model = if msg.operation == "SHOW_MORE" then { model | maxResults = model.maxResults + msg.data } else model

Elm Runtime

viewupdate Model

Msg Html

onClick

{ operation = "SHOW_MORE", data = 10 }

onClick { operation = "SHOW_MORE", data = 10 }

button [ onClick { operation = "SHOW_MORE", data = 10 } ] [ text "Show More" ]

viewupdate Model

Msg Htmlh1 [] [ text "ElmHub" ]

{ query = "tutorial"

, results = []

}

Elm Runtime

DELETE_BY_ID

viewupdate Model

Msg Htmlh1 [] [ text "ElmHub" ]

{ query = "tutorial"

, results = []

}

{ operation = "DELETE_BY_ID"

, data = 2

}

Elm Runtime

Elm Runtime

viewupdate Model

Msg Html

Exercise: resolve the TODOs in part3/Main.elmElm: (\foo -> foo + 1)

JS: (function(foo) { return foo + 1 })

{ animals | cats = animals.cats + 1 }

List.filter (\num -> num >= 2) [ 1, 2, 3 ]

== [ 2, 3 ]

4. Annotations

-- query is a string

query = "tutorial"

query : String

query = "tutorial"

stars : Int

stars = 123

searchResult : { name : String, stars : Int }

searchResult = { name = "blah", stars = 415 }

list : List String

list = [ "foo", "bar", "baz" ]

list : List Float

list = [ 1.1, 2.2, 3.3 ]

list : List Int

list = [ 1.1, 2.2, 3.3 ]

ERROR!

model :

{ query : String

, results :

List

{ id : Int

, name : String

, stars : Int

}

}

type alias SearchResult =

{ id : Int

, name : String

, stars : Int

}

type alias Model =

{ query : String

, results : List SearchResult

}

type alias SearchResult =

{ id : Int

, name : String

, stars : Int

}

type alias Msg =

{ operation : String

, data : Int

}

type alias Msg =

{ operation : String

, data : Int

}

view : Model -> Html Msg

view model =

type alias Msg =

{ operation : String

, data : Int

}

view : Model -> Html Msg

view model =

type alias Msg =

{ operation : String

, data : Int

}

view : Model -> Html Msg

view model =

button [ onClick { operation = "RESET", data = "all" } ]

[ text "Reset All" ]

view : Model -> Html String

view model =

button [ onClick "RESET" ]

[ text "Reset All" ]

view : Model -> Html Float

view model =

button [ onClick 12.34 ]

[ text "Reset All" ]

view : Model -> Html Msg

view model =

button [ onClick { operation = "RESET", data = "all" } ]

[ text "Reset All" ]

type alias Msg =

{ operation : String

, data : Int

}

update : Msg -> Model -> Model

update msg model =

type alias Msg =

{ operation : String

, data : Int

}

update : Msg -> Model -> Model

update msg model =

pluralize : String -> String -> Int -> String

elm-repl

Exercise: resolve the TODOs in part4/Main.elm

pluralize : String -> String -> Int -> String

5. Union Types

case-expressions

if msg.operation == "DELETE_BY_ID" then -- remove from modelelse if msg.operation == "LOAD_RESULTS" then -- load more resultselse -- default branch

case msg.operation of "DELETE_BY_ID" -> -- remove from model

"LOAD_RESULTS" -> -- load more results

_ -> -- default branch

union types

type Sorting = Ascending | Descending | Randomized

type Sorting = Ascending | Descending | Randomized

type Bool = True | False

type Bool = True | False

type

constantconstant

case currentSorting of Ascending -> -- sort ascending here

Descending -> -- sort descending here

Randomized -> -- sort randomized here

type Sorting = Ascending String | Descending String | Randomized

type Sorting = Ascending String | Descending String | Randomized

type

constant

type Sorting = Ascending String | Descending String | Randomized

type

function

functionconstant

type Sorting = Ascending String | Descending String | Randomized

type

function

functionconstant

String -> Sorting

case currentSorting of Ascending colName -> -- sort ascending here

Descending colName -> -- sort descending here

Randomized -> -- sort randomized here

type alias Msg = { operation : String , data : Int }

type alias Msg = { operation : String , data : Int }

{ operation = "DELETE_BY_ID" , data = 3 }

type alias Msg = { operation : String , data : Int }

{ operation = "DELETE_BY_ID" , data = 3 }

{ operation = "SET_QUERY" , data = "tutorial" }

type Msg = SetQuery String | DeleteById Int

type alias Msg = { operation : String , data : Int }

case msg of SetQuery query -> -- set query in the model here

DeleteById id -> -- delete the result with this id here

type Msg = SetQuery String | DeleteById Int

function (String -> Sorting)

Exercise: resolve the TODOs in part5/Main.elm

type Sorting = Ascending String | Descending String | Randomized

type

constantfunction (String -> Sorting)

6. Decoding JSON

parseInt

in JavaScript

parseInt("42")

parseInt("42") == 42

parseInt("42") == 42parseInt("halibut")

parseInt("42") == 42parseInt("halibut") == NaN

parseInt

String.toInt

String.toInt "42"

String.toInt "42" == Ok 42

String.toInt "42" == Ok 42

type Result = Ok somethingGood Err somethingBad

String.toInt "42" == Ok 42String.toInt "halibut"

type Result = Ok somethingGood Err somethingBad

String.toInt "42" == Ok 42String.toInt "halibut" == Err "umm halibut is not an int"

type Result = Ok somethingGood Err somethingBad

Maybe

type Result = Ok somethingGood Err somethingBad

type Result = Ok somethingGood Err somethingBad

type Maybe = Just someValue Nothing

type Maybe = Just someValue Nothing

List.head [ 5, 10, 15 ] == Just 5

type Maybe = Just someValue Nothing

List.head [ 5, 10, 15 ] == Just 5

List.head [] == Nothing

pipelines

List.filter (\num -> num < 5) [ 2, 4, 6, 8 ]

List.filter (\num -> num < 5) [ 2, 4, 6, 8 ]

List.reverse (List.filter (\num -> num < 5) [ 2, 4, 6, 8 ])

[ 2, 4, 6, 8 ] |> List.filter (\num -> num < 5) |> List.reverse

List.reverse (List.filter (\num -> num < 5) [ 2, 4, 6, 8 ])

[ 2, 4, 6, 8 ] |> List.filter (\num -> num < 5) |> List.reverse |> List.map negate

List.reverse (List.filter (\num -> num < 5) [ 2, 4, 6, 8 ])

[ 2, 4, 6, 8 ] |> List.filter (\num -> num < 5) |> List.reverse |> List.map negate |> List.head

List.reverse (List.filter (\num -> num < 5) [ 2, 4, 6, 8 ])

decoders

decodeString float "123.45"

decodeString float "123.45"

== Ok 123.45

decodeString float "123.45"

== Ok 123.45

decodeString float "blah"

decodeString float "123.45"

== Ok 123.45

decodeString float "blah"

== Err "blah is not a float!"

decodeString (list int) "[1, 2, 3]"

decodeString (list int) "[1, 2, 3]"

== Ok [ 1, 2, 3 ]

decodeString (list int) "[1, 2, 3]"

== Ok [ 1, 2, 3 ]

"[1, 2, 3]" |> decodeString (list int)

== Ok [ 1, 2, 3 ]

decoding objects into records

makeGameState score playing = { score = score, playing = playing }

makeGameState score playing = { score = score, playing = playing }

decoder = ???

makeGameState score playing = { score = score, playing = playing }

decoder = ???

decodeString decoder """{"score": 5.5, "playing": true}"""

== Ok { score = 5.5, playing = True }

makeGameState score playing = { score = score, playing = playing }

decoder = decode makeGameState |> required "score" float |> required "playing" bool

decodeString decoder """{"score": 5.5, "playing": true}"""

== Ok { score = 5.5, playing = True }

makeGameState score playing = { score = score, playing = playing }

decoder = decode makeGameState |> required "score" float |> required "playing" bool

decodeString decoder """{"score": 5.5, "playing": true}"""

== Ok { score = 5.5, playing = True }

makeGameState score playing = { score = score, playing = playing }

decoder = decode makeGameState |> required "score" float |> required "playing" bool

decodeString decoder """{"score": 5.5, "playing": true}"""

== Ok { score = 5.5, playing = True }

type alias GameState = { score : Float, playing : Bool }

makeGameState : Float -> Bool -> GameStatemakeGameState score playing = { score = score, playing = playing }

type alias GameState = { score : Float, playing : Bool }

GameState : Float -> Bool -> GameState

makeGameState : Float -> Bool -> GameStatemakeGameState score playing = { score = score, playing = playing }

type alias GameState = { score : Float, playing : Bool }

GameState : Float -> Bool -> GameState

makeGameState : Float -> Bool -> GameStatemakeGameState score playing = { score = score, playing = playing }

makeGameState 2.3 True == GameState 2.3 True

type alias GameState = { score : Float, playing : Bool }

decoder = decode GameState |> required "score" float |> required "playing" bool

decodeString decoder """{"score": 5.5, "playing": true}"""

== Ok { score = 5.5, playing = True }

type alias GameState = { score : Float, playing : Bool }

decoder = decode GameState |> required "score" float |> required "playing" bool

decodeString decoder """{"score": 5.5, "playing": true}"""

== Ok { score = 5.5, playing = True }

type alias GameState = { score : Float, playing : Bool }

decoder = decode GameState |> required "score" float |> required "playing" bool

Exercise: resolve the TODOs in part6/Main.elm

7. Client-Server Communication

function guarantees

same arguments?

same return value

Math.random()

Random.generate

Math.random()

Random.generate

pickGreeting : List String -> String

Random.generate

pickGreeting : List String -> String

Random.generate

pickGreeting : List String -> Cmd Msg

pickGreeting : List String -> String

viewupdate Model

Msg

h1 [] [ text "ElmHub" ]

{ query = "tutorial"

, results = []

}

{ operation = "DELETE_BY_ID"

, data = 2

}

Elm Runtime

Html

viewupdate Model

Msg

h1 [] [ text "ElmHub" ]

{ query = "tutorial"

, results = []

}

DeleteById 2

Elm Runtime

Html Msg

viewupdate Model

Msg

Elm Runtime

Html Msg

viewupdate Model

Msg

Elm Runtime

how the Elm Runtime talks to update Html Msg

viewupdate Model

Msg Html Msg

Elm Runtime

Cmd Msg

viewupdate Model

Msg Html Msg

Elm Runtime

Cmd MsgSetGreeting 5

pickGreeting : List String -> Cmd Msg

Html.beginnerProgram

update : Msg -> Model -> Model

Html.program

update : Msg -> Model -> ( Model, Cmd Msg )

viewupdate Model

Msg Html Msg

Elm Runtime

Cmd MsgSetGreeting 5

pickGreeting : List String -> Cmd Msg

viewupdate

Msg Html Msg

Elm Runtime

Cmd MsgSetGreeting 5

pickGreeting : List String -> Cmd Msg

( model, pickGreeting greetings )

Model

viewupdate

Msg Html Msg

Elm Runtime

Cmd Msg

( model, Cmd.none )

Model

another function guarantee

no side effects

no modifying external state

fire-and-forget HTTP POST

fire-and-forget HTTP POST

✓ same arguments, same result

fire-and-forget HTTP POST

✗ does not modify external state

✓ same arguments, same result

side effects

side effects

managed effects

Cmd

Http.getString

getString : String -> Cmd String

what if it looked like this?

getString : String -> Cmd String

what if it looked like this?

it always produces a string?

getString : String -> Task Http.Error String

getString : String -> Task Http.Error String

result if it fails

getString : String -> Task Http.Error String

result if it succeeds

Task.perform

Task Cmd

Task.perform

Task Cmdtranslate failure into a Msg

Task.perform

Task Cmdtranslate failure into a Msg

translate success into a Msg

Task.perform (\err -> ShowError err) (\json -> SetJson json) (Http.getString "https://api.github.com?q=blah")

Task Cmdtranslate failure into a Msg

translate success into a Msg

Task.perform ShowError SetJson (Http.getString "https://api.github.com?q=blah")

Task Cmdtranslate failure into a Msg

translate success into a Msg

Http.getString "https://api.github.com?q=blah" |> Task.perform ShowError SetJson

Task Cmdtranslate failure into a Msg

translate success into a Msg

cmd : Cmd Msgcmd = Http.getString "https://api.github.com?q=blah" |> Task.perform ShowError SetJson

Task Cmdtranslate failure into a Msg

translate success into a Msg

-- success gives you a StringHttp.getString url

-- success gives you a StringHttp.getString url

-- success gives you a SearchResultHttp.get searchResultDecoder url

Exercise: resolve the TODOs in part7/Main.elm

cmd : Cmd Msgcmd = Http.getString "https://api.github.com?q=blah" |> Task.perform ShowError SetJson

-- success gives you a SearchResultHttp.get searchResultDecoder url

day 1 wrap

Slack: elmlang.herokuapp.com

Helpful Resources

Weekly News: elmweekly.nl

Google Group: elm-discuss

http://elm-conf.us

Sept 15, 2016

Keynote: Evan CzaplickiLocation: St. Louis, MO

Tickets: $100