@rtfeldman - frontend masters · reverse : list val -> list val reverse : list thing -> list...

Post on 24-May-2020

27 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

elm@rtfeldman

8. JavaScript Interop

function guarantees

same arguments?same return value

no side effects

Math.random()

localStorage.foo = "bar";

access huge JS ecosystem

while maintaining guarantees

access huge JS ecosystem

"client/server" communication

"client/server" communication

Elm sends data to JS

JS sends data to Elm

"client/server" communication

Elm sends data to JS

JS sends data to Elmno direct function calls

Cmd on the Elm side

callback on JS side

Cmd Msg

Cmd Msg

Cmd msg

type variables

List.reverse [ "foo", "bar", "baz" ] == [ "baz", "bar", "foo" ]

List.reverse [ 1.1, 2.2, 3.3 ] == [ 3.3, 2.2, 1.1 ]

List.reverse [ True, False, False ] == [ False, False, True ]

reverse :

reverse : List ??? -> List ???

reverse : List val -> List val

reverse : List val -> List val

type variable

reverse : List val -> List val

reverse : List thing -> List thing

reverse : List a -> List a

elmHubHeader : Html MsgelmHubHeader = header [] [ h1 [] [ text "ElmHub" ] , span [ class "tagline" ] [ text "Like GitHub..." ] ]

elmHubHeader : Html MsgelmHubHeader = header [] [ h1 [] [ text "ElmHub" ] , span [ class "tagline" ] [ text "Like GitHub..." ] ]

"this is compatible with Html that produces Msg"

elmHubHeader : Html msgelmHubHeader = header [] [ h1 [] [ text "ElmHub" ] , span [ class "tagline" ] [ text "Like GitHub..." ] ]

elmHubHeader : Html aelmHubHeader = header [] [ h1 [] [ text "ElmHub" ] , span [ class "tagline" ] [ text "Like GitHub..." ] ]

Cmd Msg

produces messages of type Msg

Cmd Msg

works with update functions that accept Msg

produces messages of type Msg

Cmd Msg

Cmd msgworks with any update function

Cmd Msg

Cmd msgworks with any update function

...because it never produces any messages!

the port keyword

&

index.html

subscriptions

viewupdate

Msg Html Msg

Elm Runtime

Cmd Msg

Model

viewupdate

Msg Html Msg

Elm Runtime

Cmd Msg

Modelsubscriptions

viewupdate

Msg Html Msg

Elm Runtime

Cmd Msg

Modelsubscriptions Msg

viewupdate

Msg Html Msg

Elm Runtime

Cmd Msg

Modelsubscriptions Msg

Model

github.js

Exercise: resolve the TODOs in part8

decodeString responseDecoder json

decodeValue responseDecoder json

9. Testing

{ "version": "1.0.0", "summary": "Like GitHub, but for Elm stuff.", "repository": "https://github.com/rtfeldman/elm-workshop.git", "license": "BSD-3-Clause", "source-directories": [ ".", ".." ], "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0"}

elm-package.json

{ "version": "1.0.0", "summary": "Like GitHub, but for Elm stuff.", "repository": "https://github.com/rtfeldman/elm-workshop.git", "license": "BSD-3-Clause", "source-directories": [ ".", ".." ], "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0"}

elm-package.json

{ "version": "1.0.0", "summary": "Like GitHub, but for Elm stuff.", "repository": "https://github.com/rtfeldman/elm-workshop.git", "license": "BSD-3-Clause", "source-directories": [ ".", ".." ], "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0"}

elm-package.json

elmHub/ elm-package.json Main.elm ElmHub.elm

{ "version": "1.0.0", "summary": "Like GitHub, but for Elm stuff.", "repository": "https://github.com/rtfeldman/elm-workshop.git", "license": "BSD-3-Clause", "source-directories": [ ".", ".." ], "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0"}

elm-package.json

{ "version": "1.0.0", "summary": "Like GitHub, but for Elm stuff.", "repository": "https://github.com/rtfeldman/elm-workshop.git", "license": "BSD-3-Clause", "source-directories": [ ".", ".." ], "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0"}

elm-package.json

{ "version": "1.0.0", "summary": "Like GitHub, but for Elm stuff.", "repository": "https://github.com/rtfeldman/elm-workshop.git", "license": "BSD-3-Clause", "source-directories": [ ".", ".." ], "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0" }, "elm-version": "0.17.0 <= v < 0.18.0"}

elm-package.json

"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0"

"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0"

"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0"

"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0"

"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0"

1.1.2major minor patch

"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0"

1.1.2major minor patch

semantic versioning automatically enforced

elmHub/ elm-package.json Main.elm ElmHub.elm

elmHub/ elm-package.json Main.elm ElmHub.elm

tests/ elm-package.json Main.elm Tests.elm

"source-directories": [ ".", ".."],"dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-community/elm-test": "2.0.1 <= v < 3.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0", "rtfeldman/html-test-runner": "1.0.0 <= v < 2.0.0", "rtfeldman/node-test-runner": "2.0.0 <= v < 3.0.0"}

tests/elm-package.json

tests

"source-directories": [ ".", ".."],"dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-community/elm-test": "2.0.1 <= v < 3.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0", "rtfeldman/html-test-runner": "1.0.0 <= v < 2.0.0", "rtfeldman/node-test-runner": "2.0.0 <= v < 3.0.0"}

tests/elm-package.json

testsmain source

"source-directories": [ ".", ".."],"dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-community/elm-test": "2.0.1 <= v < 3.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0", "rtfeldman/html-test-runner": "1.0.0 <= v < 2.0.0", "rtfeldman/node-test-runner": "2.0.0 <= v < 3.0.0"}

tests/elm-package.json

"source-directories": [ ".", ".."],"dependencies": { "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "elm-community/elm-test": "2.0.1 <= v < 3.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0", "rtfeldman/html-test-runner": "1.0.0 <= v < 2.0.0", "rtfeldman/node-test-runner": "2.0.0 <= v < 3.0.0"}

tests/elm-package.json

package.elm-lang.org

unit tests

"[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3])

expectation : Expectation expectation = "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3])

\throwawayArgument -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3])

() empty tuple, aka "Unit"

\throwawayArgument -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3])

\() -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3])

test "it successfully decodes" ( \() -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3]) )

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

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

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

test "it successfully decodes" ( \() -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3]) )

test "it successfully decodes" <| \() -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3])

describe "decoder tests" [ test "it successfully decodes" <| \() -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3]) ]

describe "all my tests" [ describe "decoder tests" [ test "it successfully decodes" <| \() -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3]) ] ]

test "Reversing twice does nothing" <| \() -> [ 1, 2, 3 ] |> List.reverse |> List.reverse |> Expect.equal [ 1, 2, 3 ]

fuzz int "Reversing twice does nothing" <| \randomInt -> [ 1, 2, randomInt ] |> List.reverse |> List.reverse |> Expect.equal [ 1, 2, randomInt ]

fuzz (list int) "Reversing twice does nothing" <| \randomList -> randomList |> List.reverse |> List.reverse |> Expect.equal randomList

fuzz2 int float "Integers are bigger than floats" <| \randomInt randomFloat-> randomInt |> Expect.greaterThan randomFloat

fuzz2 int float "Integers are bigger than floats" <| \randomInt randomFloat-> randomInt |> Expect.greaterThan randomFloat

Exercise: resolve the TODOs in part9

test "it decodes successfully" <| \() -> "[ 1, 2, 3 ]" |> decodeString (list int) |> Expect.equal (Ok [ 1, 2, 3])

10. Delegation

Adding Search Options

Adding Search OptionssearchIn

userFilterminStars

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars Int | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , minStars : Int , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

input [] [ value model.sort, onInput SetSort ]

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

input [] [ value model.options.sort, onInput SetSort ]

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

input [] [ value model.options.sort, onInput SetSort ]

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

input [] [ value model.options.sort, onInput (Options SetSort) ]

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

input [] [ value model.options.sort, onInput (Options SetSort) ]

viewOptions : Model -> Html Msg

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

viewOptions options = input [] [ value options.sort, onInput SetSort ]

viewOptions : Model -> Html MsgviewOptions : SearchOptions -> Html OptionsMsg

input [] [ value model.options.sort, onInput (Options SetSort) ]

viewOptions : SearchOptions -> Html OptionsMsg

viewOptions options = input [] [ value options.sort, onInput SetSort ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

List StringList Int

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

List StringList Int

List.map String.length foo

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

List StringList Int

List.map String.length fooString.length : String -> Int

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

Html StringHtml Int

Html.map String.length fooString.length : String -> Int

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

Html StringHtml Int

Html.map String.length fooString.length : String -> Int

viewOptions : SearchOptions -> Html OptionsMsg

Html OptionsMsg

Html Msg

Html OptionsMsg Html.map

Html Msg

Html OptionsMsg Html.map ???

Html Msg

Html OptionsMsg OptionsMsg -> Msg Html.map ??? Html Msg

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

Html OptionsMsg OptionsMsg -> Msg Html.map ??? Html Msg

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

Html OptionsMsg OptionsMsg -> Msg Html.map ??? Html Msg

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , viewOptions model.options ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , viewOptions model.options ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map ??????? (viewOptions model.options) ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map Options (viewOptions model.options) ]

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map Options (viewOptions model.options) ]

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map Options (viewOptions model.options) , ul [] (List.map viewSearchResult model.results) ]

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map Options (viewOptions model.options) , ul [] (List.map viewSearchResult model.results) ]

viewSearchResult : SearchResult -> Html Msg

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map Options (viewOptions model.options) , ul [] (List.map viewSearchResult model.results) ]

viewSearchResult : SearchResult -> Html Msg

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map Options (viewOptions model.options) , ul [] (List.map viewSearchResult model.results) ]

viewSearchResult : SearchResult -> Html Msg

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [ class "content" ] [ input [ defaultValue model.query, onInput SetQuery ] [] , button [ onClick Search ] [ text "Search" ] , Html.map Options (viewOptions model.options) , ul [] (List.map viewSearchResult model.results) ]

viewSearchResult : SearchResult -> Html Msg

Recap

type Msg = Search | SetQuery String | DeleteById Int | SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

viewSearchResult : Model -> Html MsgviewSearchResult model = input [] [ value model.sort, onInput SetSort ]

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

viewSearchResult : Model -> Html MsgviewSearchResult model = input [] [ value model.options.sort, onInput (Options SetSort) ]

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

viewSearchResult : SearchOptions -> Html OptionsMsgviewSearchResult options = input [] [ value options.sort, onInput SetSort ]

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

type OptionsMsg = SetMinStars String | SetSearchIn String | SetUserFilter String

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

type alias SearchOptions = { minStars : Int , minStarsError : Maybe String , searchIn : String , userFilter : String }

viewSearchResult : SearchOptions -> Html OptionsMsgviewSearchResult options = input [] [ value options.sort, onInput SetSort ]

Html.map Options Html Msg

other ways to map

List.map : (originalVal -> newVal) -> List originalVal -> List newVal

List.map : (originalVal -> newVal) -> List originalVal -> List newVal

Html.map : (originalMsg -> newMsg) -> Html originalMsg -> Html newMsg

List.map : (originalVal -> newVal) -> List originalVal -> List newVal

Html.map : (originalMsg -> newMsg) -> Html originalMsg -> Html newMsg

Cmd.map : (originalMsg -> newMsg) -> Cmd originalMsg -> Cmd newMsg

List.map : (originalVal -> newVal) -> List originalVal -> List newVal

Html.map : (originalMsg -> newMsg) -> Html originalMsg -> Html newMsg

Cmd.map : (originalMsg -> newMsg) -> Cmd originalMsg -> Cmd newMsg

Sub.map : (originalMsg -> newMsg) -> Sub originalMsg -> Sub newMsg

List.map : (originalVal -> newVal) -> List originalVal -> List newVal

Html.map : (originalMsg -> newMsg) -> Html originalMsg -> Html newMsg

Cmd.map : (originalMsg -> newMsg) -> Cmd originalMsg -> Cmd newMsg

Sub.map : (originalMsg -> newMsg) -> Sub originalMsg -> Sub newMsg

delegation!

viewOptions : SearchOptions -> Html OptionsMsg

view : Model -> Html Msgview model = div [] [ input [ defaultValue model.query, onInput SetQuery ] [] , Html.map Options (viewOptions model.options) ]

type Msg = Search | SetQuery String | DeleteById Int | Options OptionsMsg

Exercise: resolve the TODOs in part10type alias Model = { query : String , results : List SearchResult , options : SearchOptions }

11. Scaling Elm Code

The Deeply Nested Component Problem

The Deeply Nested Component Problem

solving

Component

Web ComponentsReact Components

Angular ComponentsEmber ComponentsMithril ComponentsVue Components

Componentowns its own state

Header Componentowns its own state

Footer Componentowns its own state

Body Componentowns its own state

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state owns its own state

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

deeply nested components

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state choose language

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

new feature

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state choose language

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state choose language

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state choose language

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

parent-child communication

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state choose language

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state choose language

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own stateowns its own state

owns its own state owns its own state

owns its own state

owns its own state

owns its own state owns its own state owns its own state choose language

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

owns its own state

The Deeply Nested Component Problem

The Deeply Nested Component Problem

solving

The Deeply Nested Component Problem

solving

unidirectional data flow

The Deeply Nested Component Problem

solving

unidirectional data flow

Flux

The Deeply Nested Component Problem

solving

unidirectional data flow

FluxRedux

viewupdate Model

Elm Runtime

viewupdate Model

Elm Runtime

unidirectional data flow

viewupdate Model

Elm Runtime

The Elm Architecture

unidirectional data flow

quick summary

Nest stateful components to describe application

The Deeply Nested Component Problem

Nest stateful components to describe application

The Deeply Nested Component Problem

Unidirectional Data Flow

Nest stateful components to describe application

The Deeply Nested Component Problem

Unidirectional Data Flow

Nest stateful components to describe application

The Deeply Nested Component Problem

Nest stateful components to describe application

Unidirectional Data Flow

is this a problem we're allowed to not have?

is this a problem we're allowed to not have?

yes!

45,000 lines of Production Elm Code

45,000 lines of Production Elm Code

45,000 lines of Production Elm Code

simpler

45,000 lines of Production Elm Code

simpler

different

how?

45,000 lines of Production Elm Code

how do we keep code modular at scale?

45,000 lines of Production Elm Code

how do we keep code modular at scale?

how do we share code without duplication?

45,000 lines of Production Elm Code

viewupdate Model

Elm Runtime

specific techniques

viewupdate Model

Elm Runtime

specific techniques

for scaling in a modular way

viewupdate Model

Elm Runtime

specific techniques

for scaling in a modular way

for sharing code without duplication

Scaling Fundamentals

1. Model2. view3. update

viewupdate Model

Elm Runtime

Step 1: expandStep 2: refactor

Scaling Fundamentals

1. Model2. view3. update

1. When view gets painfully large, subdivide it.

Split it into smaller helper functions.

1. When view gets painfully large, subdivide it.

Split it into smaller helper functions.

1. When view gets painfully large, subdivide it.

viewSearchResult : SearchResult -> Html Msg

viewErrorMessage : Maybe String -> Html msg

viewOptions : SearchOptions -> Html OptionsMsg

Split it into smaller helper functions.

Don't change how update works!

Don't change how Model works!

1. When view gets painfully large, subdivide it.

2. When Model gets painfully large, subdivide it.

2. When Model gets painfully large, subdivide it.

Split out smaller pieces.

2. When Model gets painfully large, subdivide it.

Split out smaller pieces.

type alias Model = { query : String , results : List SearchResult , errorMessage : Maybe String , options : SearchOptions }

2. When Model gets painfully large, subdivide it.

Split out smaller pieces.

Don't change how view works!

Don't change how update works!

3. When update gets painfully large, subdivide it.

Split it into smaller helper functions.

3. When update gets painfully large, subdivide it.

Split it into smaller helper functions.

3. When update gets painfully large, subdivide it.

updateOptions : OptionsMsg -> SearchOptions -> SearchOptions

type Msg = Search | Options OptionsMsg

Split it into smaller helper functions.

Don't change how view works!

Don't change how Model works!

3. When update gets painfully large, subdivide it.

Reusing Code without Duplication

thinking in terms of functions

thinking in terms of functionsnot components

logo : Html msglogo = img [ src "logo.png" ] []

view : Model -> Html Msgview model = logo

fancyLink : String -> String -> Html msgfancyLink url caption = a [ class "fancy-link", href url ] [ text caption ]

view : Model -> Html Msgview model = fancyLink "/about" "About Us"

profile : User -> Html msgprofile user = div [ class "profile" ] [ a [ href ("/users/" ++ user.username) ] [ img [ class "user-photo", src user.photo ] [] ] ]

view : Model -> Html Msgview model = profile model.currentUser

checkbox : String -> Bool -> Html msgcheckbox caption isChecked = label [ input [ type' "checkbox" , checked isChecked ] , text caption ]

view : Model -> Html Msgview model = checkbox "enable sounds" model.enableSounds

checkbox : msg -> String -> Bool -> Html msgcheckbox toggleChecked caption isChecked = label [ input [ type' "checkbox" , checked isChecked , onClick toggleChecked ] , text caption ]

view : Model -> Html Msgview model = checkbox ToggleSounds "enable sounds" model.enableSounds

dropdown : List String -> String -> Html msgdropdown options selectedOpt = let viewOpt opt = option [ selected (opt == selectedOpt) ] [ text opt ] in select [] (List.map viewOpt options)

view : Model -> Html Msgview model = dropdown [ "cat", "dog", "orangutan" ] "cat"

dropdown : (String -> msg) -> List String -> String -> Html msgdropdown selectOpt options selectedOpt = let viewOpt opt = option [ selected (opt == selectedOpt) ] [ text opt ] in select [ onChange selectOpt ] (List.map viewOpt options)

view : Model -> Html Msgview model = dropdown ChooseSpiritAnimal [ "cat", "dog", "orangutan" ] "cat"

portable signup form

signup : { setUsername : (String -> msg) , setPassword : (String -> msg) , setFirstName : (String -> msg) , setLastName : (String -> msg) , setEmail : (String -> msg) , cancel : msg , submit : (List ValidationError -> msg) } -> { username : String , password : String , firstName : String , lastName : String , email : String } -> Html msg

view : SignupState -> Html SignupMsg

view : SignupState -> Html SignupMsg

update : SignupMsg -> SignupModel -> ( SignupModel, Cmd SignupMsg )

view : SignupState -> Html SignupMsg

update : SignupMsg -> SignupModel -> ( SignupModel, Cmd SignupMsg )

init : ( SignupModel, SignupMsg )

type alias Model = { signup : SignupModel , … }

view : Model -> Html Msgview model = Signup.view model.signup |> Html.map ...

view : SignupState -> Html SignupMsg

update : SignupMsg -> SignupModel -> ( SignupModel, Cmd SignupMsg )

init : ( SignupModel, SignupMsg )

view : SignupState -> Html SignupMsg

update : SignupMsg -> SignupModel -> ( SignupModel, Cmd SignupMsg )

init : ( SignupModel, SignupMsg )

type alias Model = { signup : SignupModel , … }

view : Model -> Html Msgview model = Signup.view model.signup |> Html.map ...

type Msg = SignupMsg SignupMsg | OtherStuff

update : Msg -> Model -> ( Model, Cmd Msg)update msg model = case msg of SignupMsg signupMsg -> ….

An Easy Mistake to Make

An Easy Mistake to Make

MVU lets me isolate state...

An Easy Mistake to Make

MVU lets me isolate state...nesting isolated state feels familiar...

!!!

An Easy Mistake to Make

MVU lets me isolate state...nesting isolated state feels familiar...

An Easy Mistake to Make

MVU lets me isolate state...nesting isolated state feels familiar...

sortable table

Exercise: resolve the TODOs in part11

type alias Model = { signup : SignupModel , … }

view : Model -> Html Msgview model = Signup.view model.signup |> Html.map ...

type Msg = SignupMsg SignupMsg | OtherStuff

update : Msg -> Model -> ( Model, Cmd Msg)update msg model = case msg of SignupMsg signupMsg -> ….

12. Performance Optimization

Collections Performance

5 :: []

5 :: []

== [ 5 ]

1 :: (5 :: [])

1 :: (5 :: [])

== [ 1, 5 ]

4 :: (1 :: (5 :: []))

4 :: (1 :: (5 :: []))

== [ 4, 1, 5 ]

4 :: (1 :: (5 :: []))

== [ 4, 1, 5 ]

adding to the front: cheap!

4 :: (1 :: (5 :: []))

== [ 4, 1, 5 ]

adding to the front: cheap!

adding to the back: expensive!

4 :: (1 :: (5 :: []))

== [ 4, 1, 5 ]

adding to the front: cheap!

adding to the back: expensive!

reading the first element: cheap!

4 :: (1 :: (5 :: []))

== [ 4, 1, 5 ]

adding to the front: cheap!

adding to the back: expensive!

reading the first element: cheap!reading other elements: expensive!

case myList of [] -> -- some logic goes here

4 :: (1 :: (5 :: []))== [ 4, 1, 5 ]

case myList of [] -> -- some logic goes here

first :: rest -> -- some logic goes here

4 :: (1 :: (5 :: []))== [ 4, 1, 5 ]

case myList of [] -> -- some logic goes here

first :: rest -> -- some logic goes here

4 :: (1 :: (5 :: []))== [ 4, 1, 5 ]

case myList of [] -> -- some logic goes here

first :: rest -> -- some logic goes here

4 :: (1 :: (5 :: []))== [ 4, 1, 5 ]

case myList of [] -> -- some logic goes here

singleton :: [] -> -- some logic goes here

first :: rest -> -- some logic goes here

4 :: (1 :: (5 :: []))== [ 4, 1, 5 ]

countEmptyStrings : List String -> Int

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

first :: rest ->

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

first :: rest -> if first == "" then 1 + (countEmptyStrings rest)

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

first :: rest -> if first == "" then 1 + (countEmptyStrings rest) else countEmptyStrings rest

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

first :: rest -> if first == "" then 1 + (countEmptyStrings rest) else countEmptyStrings rest

recurse, then immediately return

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

first :: rest -> if first == "" then 1 + (countEmptyStrings rest) else countEmptyStrings rest

recurse, then immediately return: tail call!

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

first :: rest -> if first == "" then 1 + (countEmptyStrings rest) else countEmptyStrings rest

recurse, then do something else before returning: not tail call

countEmptyStrings : List String -> IntcountEmptyStrings list = case list of [] -> 0

first :: rest -> if first == "" then 1 + (countEmptyStrings rest) else countEmptyStrings rest

myArray : Array FloatmyArray = Array.fromList [ 1.1, 2.2, 3.3 ]

myArray : Array FloatmyArray = Array.fromList [ 1.1, 2.2, 3.3 ]

adding to the front: less cheap

adding to the back: less expensive

myArray : Array FloatmyArray = Array.fromList [ 1.1, 2.2, 3.3 ]

adding to the front: less cheap

adding to the back: less expensive

reading any element: cheap!removing first element: expensive!

flavors : Dict String Intflavors = Dict.fromList [ ( "vanilla", 5 ) , ( "chocolate", 9 ) , ( "strawberry", 7 ) ]

Dict.get "strawberry" flavors

flavors : Dict String Intflavors = Dict.fromList [ ( "vanilla", 5 ) , ( "chocolate", 9 ) , ( "strawberry", 7 ) ]

Dict.get "strawberry" flavors == Just 7

flavors : Dict String Intflavors = Dict.fromList [ ( "vanilla", 5 ) , ( "chocolate", 9 ) , ( "strawberry", 7 ) ]

Dict.remove "strawberry" flavors == Dict.fromList [ ( "vanilla", 5 ) , ( "chocolate", 9 ) ]

flavors : Dict String Intflavors = Dict.fromList [ ( "vanilla", 5 ) , ( "chocolate", 9 ) , ( "strawberry", 7 ) ]

div [ class "flavor-list" ] flavors

flavors : Dict String Intflavors = Dict.fromList [ ( "vanilla", 5 ) , ( "chocolate", 9 ) , ( "strawberry", 7 ) ]

div [ class "flavor-list" ] flavors

flavors : Dict String Intflavors = Dict.fromList [ ( "vanilla", 5 ) , ( "chocolate", 9 ) , ( "strawberry", 7 ) ]

Rendering from a Dict

Dict.values

List.sortBy

Dict.values

Skipping Virtual DOM building

viewMenu : List MenuItem -> Html MsgviewMenu items = -- lots of view code goes here

viewMenu : List MenuItem -> Html MsgviewMenu items = -- lots of view code goes here

view : Model -> Html Msgview model = -- lots of other code goes here...

viewMenu model.menuItems

viewMenu : List MenuItem -> Html MsgviewMenu items = -- lots of view code goes here

view : Model -> Html Msgview model = -- lots of other code goes here...

lazy viewMenu model.menuItems

viewMenu : Config -> List MenuItem -> Html MsgviewMenu config items = -- lots of view code goes here

view : Model -> Html Msgview model = -- lots of other code goes here...

lazy2 viewMenu model.config model.menuItems

Debug.log

Debug.log "some numbers" [ 1, 2, 3 ]

someNumbers = Debug.log "some numbers" [ 1, 2, 3 ]

someNumbers = [ 1, 2, 3 ]

Debug.log "some numbers"

requestAnimationFrame

viewupdate Model

Elm Runtime

viewupdate Model

Elm Runtime

browser repaints at ~60fps

viewupdate Model

Elm Runtime

browser repaints at ~60fps

computing virtual DOM more often than that is a waste!

viewupdate Model

Elm Runtime

browser repaints at ~60fps

computing virtual DOM more often than that is a waste!

keep updating model and running commands

viewupdate Model

Elm Runtime

browser repaints at ~60fps

computing virtual DOM more often than that is a waste!

keep updating model and running commandsdon't bother running view until the next repaint

viewupdate Model

Elm Runtime

keep updating model and running commandsdon't bother running view until the next repaint

how do we get this?

viewupdate Model

Elm Runtime

keep updating model and running commandsdon't bother running view until the next repaint

This is just how Elm rolls.

don't count on Debug.log in views!

Exercise: resolve the TODOs in part13

lazy

Debug.log "foo is" foo

13. Tools

HTML-to-Elm

JSON-to-Elm

sample JSON

Error Message Catalog

create-elm-app

elm-format

elm-reactor

elm-css

Built With Elm: builtwithelm.co

Links of Interest

Elm Community: elm-community.org

NRI Tech Blog: tech.noredink.com

Exercise: resolve the TODO in ElmHubCss.elm

package.elm-lang.org/packages/rtfeldman/elm-css/latest

Implement additional search options:

ElmHub Hacking!

https://developer.github.com/v3/search/#search-repositories

Refactor to results : Dict Int SearchResult

Move SearchOptions into its own module

Migrate tests from part9 to part14 and add tests for update

top related