I'll show you some content on some slides where we'll chat things through.
I'll then give you some exercises to do on your machine and answer any questions.
Setup instructions:
Most JS developers today are writing JavaScript that gets compiled into "older" JavaScript that can run in more Browsers.
But if our code is getting compiled anyway...could we use something other than JS that gives us benefits?
const add = (x, y) => x + y
add x y = x + y
add x y =
x + y
Elm
JS
No parens or commas with function arguments
function name comes first, no need for const/let/var
no explicit return keyword, ever
const pluralize = (singular, plural, count) => {
if (count === 1) return singular
return plural
}
pluralize("apple", "apples", 5) => "apples"
pluralize singular plural number =
if number == 1 then
singular
else
plural
pluralize "apple" "apples" 5
Elm
JS
npm run exercise exercise1
yarn run exercise exercise1
http://localhost:8000
module Main exposing (..)
import Html exposing (..)
pluralize singular plural number =
plural
main =
text (pluralize "apple" "apples" 5)
module Main exposing (..)
import Html exposing (..)
pluralize singular plural number =
plural
main =
text (pluralize "apple" "apples" 5)
Each Elm file is a module. The name matches the file name.
module Main exposing (..)
import Html exposing (..)
pluralize singular plural number =
plural
main =
text (pluralize "apple" "apples" 5)
here we're importing the HTML module (we'll look more at this later...)
module Main exposing (..)
import Html exposing (..)
pluralize singular plural number =
plural
main =
text (pluralize "apple" "apples" 5)
Elm looks for a function called main, and will run it if it finds it
module Main exposing (..)
import Html exposing (..)
pluralize singular plural number =
plural
main =
text (pluralize "apple" "apples" 5)
`text` is an Html function that outputs Html to the page
module Main exposing (..)
import Html exposing (..)
pluralize singular plural number =
plural
main =
text (pluralize "apple" "apples" 5)
we pass text the result of calling pluralize
pluralize singular plural number =
plural
-- THIS IS A COMMENT
can you fill this function body in?
http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html
div [ class "content" ] [ text (pluralize "apple" "apples" 5) ]
div [ class "content" ] [ text (pluralize "apple" "apples" 5) ]
div
[ class "content" ]
[ text "Hello world" ]
first argument is a list of attributes
second is a list of children elements
main =
div [ class "content" ]
[ text (pluralize "apple" "apples" 5)
]
pluralize singular plural number =
if number == 1 then
singular
else
plural
pluralize "apple" "apples" "woops"
this is wrong - should be a number
pluralize : String -> String -> Int -> String
pluralize singular plural number =
the compiler is great at inferring types
but if we know them, we can add them
the right hand type is the return type
fix the type error that's preventing the app from compiling
and add the type annotation to pluralize
jack = ("jack", 25)
Tuple.first jack == "jack"
Tuple.second jack == 25
otherJack = ("jack", 25, "london")
-- you cannot use Tuple.first or Tuple.second
-- on tuples of length != 2
can contain data of different types, but you can't give any of the data names
stick to 2/3 items at most.
jack =
{ name = "jack"
, age = 25
, city = "london"
}
jack.name == "jack"
jack.age == 25
jack.city == "london"
you cannot add or remove items
types within them can be mixed
fruits =
{ apple = ( "apple", "apples" )
, banana = ( "banana", "bananas" )
, grape = ( "grape", "grapes" )
}
main =
div [ class "content" ]
[ h1 [] [ text "Lots of fruits" ]
, text (pluralize (Tuple.first fruits.banana) (Tuple.second fruits.banana) 5)
, hr [] []
, text (pluralize (Tuple.first fruits.apple) (Tuple.second fruits.apple) 5)
, hr [] []
, text (pluralize (Tuple.first fruits.grape) (Tuple.second fruits.grape) 1)
]
text
(pluralize (Tuple.first fruits.banana) (Tuple.second fruits.banana) 5)
text
(pluralize "banana" "bananas" 5)
if you're thinking we could write this more neatly...you'd be correct! We'll get there soon :)
more type annotations!
fruits :
{ apple : ( String, String )
, banana : ( String, String )
, grape : ( String, String )
}
pluralize (Tuple.first fruits.banana) (Tuple.second fruits.banana) 5
-- becomes:
pluralize fruits.banana 5
apple : ( String, String )
pluralize: ( String, String ) -> Int -> String
pluralize fruit number =
if number == 1 then
Tuple.first fruit
else
Tuple.second fruit
type alias Fruit = ( String, String )
apple : Fruit
pluralize: Fruit -> Int -> String
pluralize fruit number =
if number == 1 then
Tuple.first fruit
else
Tuple.second fruit
type alias Fruit = ( String, String )
apple : Fruit
pluralize: Fruit -> Int -> String
pluralize fruit number =
if number == 1 then
Tuple.first fruit
else
Tuple.second fruit
people = ["Jack", "Alice", "Bob"]
jack = ["Jack", 2, 2.22] --NOPE
this is invalid
Lists must all have the same type.
people = ["Jack", "Alice", "Bob"]
jack = ["Jack", 2, 2.22] --NOPE
we say this list is of type: List String
type alias Fruit =
( String, String )
fruits : List Fruit
fruits =
[ ( "apple", "apples" ), ( "banana", "bananas" ) ]
type alias Fruit =
( String, String, Int )
fruits : List Fruit
fruits =
[ ( "apple", "apples", 2 ), ( "banana", "bananas", 1 ) ]
pluralize : Fruit -> String
pluralize fruit =
-- HOW DO WE GET number from the fruit Tuple?
if number == 1 then
Tuple.first fruit
else
Tuple.second fruit
pluralize : Fruit -> String
pluralize ( singular, plural, number ) =
if number == 1 then
singular
else
plural
renderFruit : Fruit -> Html a
renderFruit fruit =
text (pluralize fruit)
ignore this for now...we'll get there soon!
renderFruit : Fruit -> Html a
renderFruit fruit =
text (pluralize fruit)
text <| pluralize fruit
pluralize fruit |> text
these are all equivalent
renderFruit : Fruit -> Html a
renderFruit =
pluralize >> text
don't worry if you don't understand this - this takes some time and isn't important for now!
List.map (\a -> a * 2) [1, 2, 3]
-- [2, 4, 6]
http://package.elm-lang.org/packages/elm-lang/core/5.1.1/List
this is how we create anonymous functions in Elm, just like in JS
apply the function to each item in this list
double a = a * 2
List.map (\a -> a * 2) [1, 2, 3]
List.map double [1, 2, 3]
-- [2, 4, 6]
http://package.elm-lang.org/packages/elm-lang/core/5.1.1/List
main =
div [ class "content" ]
[ h1 [] [ text "Lots of fruits" ]
, div [] (List.map renderFruit fruits)
]
phew!
renderFruit : Fruit -> Html a
renderFruit fruit =
text (pluralize fruit)
type alias Fruit =
{ singular : String
, plural : String
, number : Int
}
The definition of `fruits` does not match its type annotation.
18| fruits : List Fruit
19| fruits =
20|> [ ( "apple", "apples", 2 ), ( "banana", "bananas", 1 ) ]
The type annotation for `fruits` says it is a:
List Fruit
But the definition (shown above) is a:
List ( String, String, number )
fruits : List Fruit
fruits =
[ { singular = "apple", plural = "apples", number = 4 }
, { singular = "banana", plural = "bananas", number = 1 }
]
This tuple is causing problems in this pattern match.
26| pluralize ( singular, plural, number ) =
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The pattern matches things of type:
( a, b, c )
But the values it will actually be trying to match are:
Fruit
Detected errors in 1 module.
pluralize : Fruit -> String
pluralize ( singular, plural, number ) =
if number == 1 then
singular
else
plural
pluralize : Fruit -> String
pluralize { singular, plural, number } =
if number == 1 then
singular
else
plural
you can destructure records too by using curly braces
get the fruits rendering onto the page again
Lists | Tuples | Records |
---|---|---|
✔ can iterate | ❌ cannot iterate | ❌ cannot iterate |
❌all of same type | ✔ can have mixed types within | ✔ can have mixed types within |
❌individual items can't be named | ❌individual items can't be named | ✔ individual items can be named |
the data of your application
how we render the app
how to deal with events and user interaction
Elm Runtime
view
Html
model
update
main =
Html.beginnerProgram
{ model = initialModel
, view = view
, update = update
}
type alias Model =
{ fruits : List Fruit
}
--- the name of the alias doesn't matter
--- but Model is the convention you'll see everywhere
fruits : List Fruit
fruits =
[ { singular = "apple", plural = "apples", number = 4 }
, { singular = "banana", plural = "bananas", number = 1 }
]
initialModel : Model
initialModel =
{ fruits = fruits }
view : Model -> Html a
view model =
div
[ class "content" ]
[ h1 [] [ text "Lots of fruits" ]
, div [] (List.map renderFruit [])
]
notice that view takes the model as its argument
so it can render data from what's in the model
update : a -> Model -> Model
update msg model =
model
ignore this for now! we're going to come onto this in the next exercise :)
fix the view so it shows our fruits properly
and have a play with the code to get used to the model/view/update pattern
import Html.Events exposing (onClick)
view : Model -> Html a
a here is a placeholder for "some type that we don't know about"
sometimes we don't always know exactly what type we'll be given
List.singleton "foo" --> ["foo"]
List.singleton 2 --> [2]
singleton : a -> List a
singleton a = [a]
this annotation says we take some `a`, and return a List of type `a`
singleton : a -> List a
singleton a = [a]
List.singleton "foo" --> ["foo"] List String
List.singleton 2 --> [2] List Int
Elm Runtime
view
Html String
model
update
User actions produce String
view : Model -> Html a
"the view function takes a model and produces HTML that creates messages of type a"
user clicks button
elm runtime generates a message of type `a`
type alias Model =
{ count : Int
}
initialModel : Model
initialModel =
{ count = 0 }
view : Model -> Html String
view model =
div
[ class "content" ]
[ h1 [] [ text "Click to increment" ]
, div []
[ button [ onClick "INCREMENT" ]
[ text "Increment" ]
, text (toString model.count)
]
]
view : Model -> Html String
view model =
div
[ class "content" ]
[ h1 [] [ text "Click to increment" ]
, div []
[ button [ onClick "INCREMENT" ]
[ text "Increment" ]
, text (toString model.count)
]
]
onClick "INCREMENT" means we create messages of type String, so our view is producing Html String
update : String -> Model -> Model
update msg model =
if msg == "INCREMENT" then
-- do something here
else
model
notice that the first argument to update is of type String, because it's taking the messages that our Html produces.
model = { count = 0 }
{ model | count = 1 } -- { count = 1 }
{ model | count = model.count + 1 } -- { count = 1 }
remember that all data is immutable, so doing this creates a new record, and leaves the old one intact!
update : String -> Model -> Model
update msg model =
if msg == "INCREMENT" then
{ model | count = model.count + 1 }
else
model
the decrement button
update : String -> Model -> Model
update msg model =
if msg == "INCREMENT" then
-- code here removed to save space!
view : Model -> Html String
view model =
div
[ class "content" ]
[ h1 [] [ text "Click to increment" ]
, div []
[ button [ onClick "INCREENT" ]
[ text "Increment" ]
, text (toString model.count)
]
]
update : String -> Model -> Model
update msg model =
if msg == "INCREMENT" then
-- code here removed to save space!
view : Model -> Html String
view model =
div
[ class "content" ]
[ h1 [] [ text "Click to increment" ]
, div []
[ button [ onClick "INCREENT" ]
[ text "Increment" ]
, text (toString model.count)
]
]
message typo!
in general, if you make a mistake and the compiler doesn't tell you about it, you're not doing it in the "Elm" way
if msg == "INCREMENT" then
...
else if msg == "DECREMENT" then
...
else
...
case msg of
"INCREMENT" ->
{ model | count = model.count + 1 }
"DECREMENT" ->
{ model | count = model.count - 1 }
_ ->
model
type Msg = Increment | Decrement
type Bool
= True
| False
this is the type
and these are constants
so we say "True is of type Bool"
type Msg = Increment | Decrement
update : Msg -> Model -> Model
now update can only take messages of type Msg, which are Increment or Decrement. This is enforced by the compiler for us!
(note: you could call this type anything, but Msg is the Elm convention)
view : Model -> Html Msg
view model =
div
[ class "content" ]
[ h1 [] [ text "Click to increment" ]
, div []
[ button [ onClick Increment ]
[ text "Increment" ]
, text (toString model.count)
]
]
now our view produces Html Msg
type Msg
= Increment
| Decrement
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
This `case` does not have branches for all possibilities.
29|> case msg of
30|> Increment ->
31|> { model | count = model.count + 1 }
You need to account for the following values:
Main.Decrement
Cannot find pattern `Derement`
33| Derement ->
^^^^^^^^
Maybe you want one of the following?
Decrement
exercise 10 does not compile - can you fix it?
type Msg
= Increment
| IncrementBy Int
| Decrement
IncrementBy is a function
IncrementBy: Int -> Msg
IncrementBy 5 == Msg
button
[ onClick (IncrementBy 5) ]
[ text "Increment by 5" ]
update : Msg -> Model -> Model
update msg model =
case msg of
IncrementBy x ->
{ model | count = model.count + x }
IncrementBy 5
x == 5
you can destructure on union types
DecrementBy
import Html.Attributes exposing (class, type_, value)
import Html.Events exposing (onClick, onInput)
if you've used React or others, you might know this as onChange
input
[ type_ "number"
, onInput NewUserIncrementInput
, value model.userInput
]
[]
type Msg
= Increment
| NewUserIncrementInput String
update : Msg -> Model -> Model
update msg model =
case msg of
NewUserIncrementInput newValue ->
{ model | userInput = newValue }
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + model.userInput }
The right side of (+) is causing a type mismatch.
28|model.count + model.userInput
^^^^^^^^^^^^^^^
(+) is expecting the right side to be a:
Int
But the right side is:
String
Hint: To append strings in Elm, you need to use the (++) operator, not (+).
String.toInt : String -> Result String Int
?????
toInt "LOL" -- ????
toInt "3" -- totally fine
type Result error value
= Ok value
| Err error
type Result error value
= Ok value
| Err error
String.toInt "123" == Ok 123
String.toInt "-42" == Ok -42
String.toInt "3.1"
== Err "could not convert string '3.1' to an Int"
String.toInt "31a"
== Err "could not convert string '31a' to an Int"
case msg of
Increment ->
case String.toInt model.userInput of
Ok number ->
{ model | count = model.count + number }
Err _ ->
model
case String.toInt model.userInput of
Ok number ->
...
Err _ ->
...
case String.toInt model.userInput of
Ok number ->
{ model | count = model.count + number }
Err _ ->
model
let
userInputAsNumber = String.toInt model.userInput
in
case userInputAsNumber of
Ok number ->
{ model | count = model.count + number }
Err _ ->
model
let
userInputAsNumber = String.toInt model.userInput
in
-- userInputAsNumber is only available within the `in`
let
userInputAsNumber = String.toInt model.userInput
in
...
case String.toInt model.userInput of
Ok number ->
{ model | count = model.count + number }
Err _ ->
model
withDefault : a -> Result x a -> a
Result.withDefault 0 (String.toInt "123") == 123
Result.withDefault 0 (String.toInt "abc") == 0
withDefault : a -> Result x a -> a
Result.withDefault 0 (String.toInt "123") == 123
Result.withDefault 0 (String.toInt "abc") == 0
withDefault : a -> Result x a -> a
Result.withDefault 0 (String.toInt "123") == 123
Result.withDefault 0 (String.toInt "abc") == 0
type alias Fruit =
{ name : String, count : Int }
type alias Model =
{ fruits : List Fruit
, userFruitNameInput : String
, userFruitCountInput : String
}
type Msg
= StoreNewFruit
| UserFruitNameInput String
| UserFruitCountInput String
initialModel : Model
initialModel =
{ fruits = [ { name = "Apple", count = 5 }, { name = "Banana", count = 4 } ]
, userFruitNameInput = ""
, userFruitCountInput = "0"
}
update : Msg -> Model -> Model
update msg model =
case msg of
StoreNewFruit ->
-- EXERCISE: can you make this add a fruit to the fruits list?
-- you will need to parse the userFruitCountInput to an integer
-- and then create a new fruit to add to model.fruits
model
-- adding to a list
"bar" :: ["foo"] == ["bar", "foo"]
List.append ["bar"] ["foo"] == ["bar", "foo"]
["bar"] ++ ["foo"] == ["bar", "foo"]
type FruitSorting = CountAsc | CountDesc
type Msg
= StoreNewFruit
| SortFruit FruitSorting
| UserFruitNameInput String
| UserFruitCountInput String
, div [ class "sorting" ]
[ button [ onClick (SortFruit CountAsc) ] [ text "Count Asc" ]
, button [ onClick (SortFruit CountDesc) ] [ text "Count Desc" ]
]
List.sort [3, 1, 5] == [1, 3, 5]
let fruits = [
{ name = "apple", count = 4 }
, { name = "banana", count = 2 }
]
List.sortBy (\fruit -> fruit.count) fruits
== [ banana, apple ]
List.sortBy (\fruit -> fruit.count) fruits
let fruit = { name = "apple", count = 4 }
.count fruit == 4
List.sortBy .count fruits
List.reverse (List.sortBy (\fruit -> fruit.count) model.fruits)
--clearer:
List.sortBy (\fruit -> fruit.count) model.fruits
|> List.reverse
-- even better:
model.fruits
|> List.sortBy .count
|> List.reverse
model.fruits
|> List.sortBy .count
|> List.reverse
type alias Model =
{ fruits : List Fruit
, userFruitNameInput : String
, userFruitCountInput : String
, fruitSorting : FruitSorting
}
type FruitSorting
= CountAsc
| CountDesc
initialModel : Model
initialModel =
{ fruits = [ { name = "Apple", count = 5 }, { name = "Banana", count = 4 } ]
, userFruitNameInput = ""
, userFruitCountInput = "0"
, fruitSorting = CountAsc
}
case msg of
SortFruit sortType ->
{ model | fruitSorting = sortType }
renderFruits : List Fruit -> FruitSorting -> Html Msg
renderFruits fruits fruitSorting =
-- EXERCISE: can you take fruitSorting into account here
-- and order the fruits in the proper order based on the value
-- of fruitSorting
ul [] (List.map renderFruit fruits)
update renderFruits to take into account our sort state
const fetchStuff = () => fetch('/url')
side effect: HTTP request
Elm Runtime
view
Html Msg
model
update
Msg
Cmd Msg
"hey Elm, do some work
for me"
Html.beginnerProgram
update: Msg -> Model -> Model
Html.program
update: Msg -> Model -> (Model, Cmd Msg)
new model (just like everything we've done so far today)
work for the Elm runtime to do for us in the background
type alias Model =
{ latestNumber : Int
}
type Msg
= NewRandomNumber Int
| RollDice
initialModel : Model
initialModel =
{ latestNumber = 0
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
RollDice ->
( model, Cmd.none )
NewRandomNumber x ->
( model, Cmd.none )
Random.generate
NewRandomNumber
(Random.int 1 6)
type: Cmd Msg
the Msg we want it to send us back
what to generate
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
RollDice ->
( model, Random.generate NewRandomNumber (Random.int 1 6) )
NewRandomNumber x ->
( { model | latestNumber = x }, Cmd.none )
get the dice rolling :)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
RollDice ->
( model, Random.generate NewRandomNumber (Random.int 1 6) )
NewRandomNumber x ->
( { model | latestNumber = x }, Cmd.none )
type alias Model =
{ latestNumber : Int
}
latestNumber = -1 ???
type Maybe a
= Just a
| Nothing
type alias Model =
{ latestNumber : Maybe Int
}
we might have an Integer
but we might have Nothing
type alias Model =
{ latestNumber : Maybe Int
}
case model.latestNumber of
Just x ->
-- we have a value, x
Nothing ->
-- we have no value yet
x = 2 -- type: Int
y = Just 3 -- type: Maybe Int
z = Nothing -- type: Maybe Int
x = 2 -- type: Int
y = Just 3 -- type: Maybe Int
z = Nothing -- type: Maybe Int
fetch('/api')
.then(response => response.json())
.then(data => ...)
JSON: { "name": "Jack" }
-- and we want:
ELM: { name = "Jack" }
import Json.Decode as JD
nameDecoder =
JD.field "name" JD.string
creates a decoder that can parse the "name" field from a JSON object, as a string
import Json.Decode as JD
nameDecoder =
JD.field "name" JD.string
JD.decodeString nameDecoder """{"name": "jack"}"""
-- (Ok "jack")
we pass a decoder to decodeString to run it against some JSON
import Json.Decode as JD
nameDecoder =
JD.field "name" JD.string
personDecoder =
JD.map
(\name -> { name = name })
nameDecoder
JD.decodeString personDecoder """{"name": "jack"}"""
-- (Ok { name = "jack" })
We use JD.map to parse JSON into records
import Json.Decode as JD
nameDecoder = JD.field "name" JD.string
ageDecoder = JD.field "age" JD.int
personDecoder =
JD.map2
(\name, age -> { name = name, age = age })
nameDecoder
ageDecoder
JD.decodeString
personDecoder
"""{"name": "jack", "age": 25}"""
-- (Ok { name = "jack", age = 25 })
what if we have multiple fields?
these decode the individual fields
that get passed to the function that's the first argument
type alias Person = {name: String, age: Int}
Person: String -> Int -> Person
Person "Jack" 25 == { name = "Jack", age = 25 }
import Json.Decode as JD
type alias Person = {name: String, age: Int}
nameDecoder = JD.field "name" JD.string
ageDecoder = JD.field "age" JD.int
personDecoder =
JD.map2
Person
nameDecoder
ageDecoder
JD.decodeString
personDecoder
"""{"name": "jack", "age": 25}"""
-- (Ok { name = "jack", age = 25 })
we can take advantage of type constructors
type alias Model =
{ person : Maybe (Result String Person)
}
personDecoder : JD.Decoder a
personDecoder =
JD.fail "You have not built the decoder yet!"
nameDecoder = JD.field ...
ageDecoder = JD.field ...
personDecoder =
JD.map2 ...
{
login: "jackfranklin",
id: 193238,
name: "Jack Franklin",
company: "@thread ",
blog: "http://www.jackfranklin.co.uk",
location: "London",
bio: "JavaScript, React, ES2015+ and Elm.",
public_repos: 257,
public_gists: 71
}
Elm has its own HTTP library but it's not built into the core, so you need to install it.
(I've already installed it for us into our project!)
type alias Person =
{ name : String
, publicRepoCount : Int
}
type alias Model =
{ person : Maybe Person
}
type Msg
= FetchPerson
| GotPerson (Result Http.Error Person)
update msg model =
case msg of
FetchPerson ->
( model, fetchPerson )
fetchPerson = Cmd.none
fetchPerson =
let
url = "..."
request =
Http.get url personDecoder
in
Http.send GotPerson request
fetchPerson =
let
url = "..."
request =
Http.get url personDecoder
in
Http.send GotPerson request
the decoder to deal with the JSON response
the Msg that will be generated once the request has been made
GotPerson (Result Http.Error Person)
fetchPerson =
let
url = "..."
request =
Http.get ...
in
Http.send PutSomethingHere request
A way to send data from Elm to JavaScript.
A way for the Elm runtime to listen to data that JavaScript sends back to it.
Elm Runtime
view
Html Msg
model
update
Msg
Cmd Msg
"hey Elm, do some work
for me"
subscriptions
port githubSearch : String -> Cmd msg
-- the Cmd that we will send from Elm to JS: githubSearch "jackfranklin"
port githubResults : (JD.Value -> msg) -> Sub msg
-- the subscription that we'll get back
-- JD.Value here is a type meaning "Some JS value that we need to decode"
port githubResults : (JD.Value -> msg) -> Sub msg
-- the subscription that we'll get back
-- JD.Value here is a type meaning "Some JS value that we need to decode"
main : Program Never Model Msg
main =
Html.program
{ init = init
, view = view
, update = update
, subscriptions = \_ -> githubResults handleGithubResponse
}
app.ports.githubSearch.subscribe(value => ...)
app.ports.githubResponse.send(data)
app.ports.githubSearch.subscribe(value => ...)
app.ports.githubResponse.send(data)
handleGithubResponse : JD.Value -> Msg
handleGithubResponse valueFromGithubJS =
...
@Jack_Franklin
jack@jackfranklin.net
javascriptplayground.com - Elm course coming soon!
I will leave all the GitHub repository and slides available to you via the online links so you can refer to them :)
elm package install NoRedInk/elm-decode-pipeline
personDecoder : JD.Decoder Person
personDecoder =
JD.map2 Person
(JD.field "name" JD.string)
(JD.field "age" JD.int)
what if we had more fields?
import Json.Decode.Pipeline as JP
personDecoder : JD.Decoder Person
personDecoder =
JP.decode Person
|> JP.required "name" JD.string
|> JP.required "age" JD.int
import Json.Decode.Pipeline as JP
personDecoder : JD.Decoder Person
personDecoder =
JP.decode Person
|> JP.required "name" JD.string
|> JP.required "age" JD.int
personDecoder : JD.Decoder Person
personDecoder =
JD.map2 Person
(JD.field "name" JD.string)
(JD.field "age" JD.int)
Old code
with pipeline