Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Language Atlas (mini-courses for more languages)  Module 12 – Elm and The Elm Architecture

Elm for JavaScript developers

Map JavaScript habits to Elm: records and curried functions, custom types and case, Maybe and Result instead of null and throw, and how Elm divides.

  • Beginner
  • 20 minutes
  • Examples run with Python 3.14.8, Node.js 24.21.0, quickjs 0.32.0 and Elm 0.19.3
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Write records, curried functions and pipelines, and update a record without changing the original
  • Model a value with several states as a custom type and handle every state with case
  • Replace null and exceptions with Maybe and Result, and chain checks with Result.andThen
  • Predict what Elm's //, modBy, remainderBy, round and truncate return, for negative and large values too
  • Fix a compiler error from its message, and make a build script trust the exit status of elm make

Before you start

What you’ll learn · Before you start

What you will learn

  • Write records, curried functions and pipelines, and update a record without changing the original
  • Model a value with several states as a custom type and handle every state with case
  • Replace null and exceptions with Maybe and Result, and chain checks with Result.andThen
  • Predict what Elm's //, modBy, remainderBy, round and truncate return, for negative and large values too
  • Fix a compiler error from its message, and make a build script trust the exit status of elm make

Before you start

On this page

In JavaScript, { loading: false, error: null, data: null } is a valid state that no screen knows how to draw, and Number("4e1") turns a typed quantity into 40. In Elm, with the screen’s states as one type, the first cannot be written, and the second gives Nothing: the language has no null, no exceptions for expected failures and no automatic conversions.

Here you map what you write every day in JavaScript to Elm: records and functions that take their arguments one at a time, custom types and case for values with several states, Maybe and Result for absence and failure, numbers that divide and round in three ways you would not guess, and compiler errors that you fix from their messages. Every Elm program here ran on Elm 0.19.3, through a worker under Node.js 24.21.0.

Values never change, and a function takes one argument at a time

A JavaScript object can be changed by any code that holds it. An Elm record cannot be changed at all: there is no assignment. { chai | quantity = 3 } is a record update: Elm's { record | field = value }, which makes a new record with some fields changed and leaves the original as it was, like { ...record, field: value } in JavaScript. Elm has no assignment to a field. that makes a new record with one field different, as { ...chai, quantity: 3 } does in JavaScript, and chai itself stays as it was. Functions work differently too. withTax : Int -> Int -> Int reads “takes an Int, and returns a function that takes an Int and returns an Int”: every Elm function is curried function: A function that takes its arguments one at a time: given the first, it returns a function that waits for the rest. Every Elm function works this way, so withTax 5 is itself a function that adds five percent., so withTax 5 is already a function that adds five percent, ready for List.map:

Records.elm: a record update, a curried function and a pipeline ELM · basics/src/Records.elm
port module Records exposing (main)

{-| Records that never change, a record update, a function that takes one
argument at a time, and a pipeline.
-}


port out : String -> Cmd msg


type alias Line =
    { item : String
    , paise : Int
    , quantity : Int
    }


chai : Line
chai =
    { item = "chai", paise = 1200, quantity = 2 }


{-| Adds a tax of `percent` to an amount in paise, rounding a half paisa up.
-}
withTax : Int -> Int -> Int
withTax percent paise =
    paise + (paise * percent + 50) // 100


lineTotal : Line -> Int
lineTotal line =
    line.paise * line.quantity


row : String -> String -> String
row label value =
    String.padRight 26 ' ' label ++ value


report : List String
report =
    let
        more =
            { chai | quantity = 3 }

        withFivePercent =
            withTax 5
    in
    [ row "chai.quantity" (String.fromInt chai.quantity)
    , row "more.quantity" (String.fromInt more.quantity)
    , row "chai.quantity again" (String.fromInt chai.quantity)
    , row "withTax 5 1200" (String.fromInt (withTax 5 1200))
    , row "withFivePercent 1450" (String.fromInt (withFivePercent 1450))
    , row "List.map withFivePercent" (String.join ", " (List.map (withFivePercent >> String.fromInt) [ 1200, 1450, 999 ]))
    , row "pipeline" ([ chai, more ] |> List.map lineTotal |> List.sum |> String.fromInt)
    ]


main : Program () () Never
main =
    Platform.worker
        { init = \_ -> ( (), out (String.join "\n" report) )
        , update = \_ model -> ( model, Cmd.none )
        , subscriptions = \_ -> Sub.none
        }

The session builds each worker of basics/ with --optimize and runs it through run.cjs, which prints what the worker sends out through its port, as in the previous lesson:

Records, a custom type, and Maybe and Result, built and run Python · basics_session.py
"""Builds and runs the three workers of basics/ that map JavaScript habits to Elm: records and functions, a custom type
with case, and Maybe and Result (session.py plays it)."""

from session import cache, run

cache()
run("""
cd basics
elm make src/Records.elm --optimize --output=build/records.js
node run.cjs Records
elm make src/Status.elm --optimize --output=build/status.js
node run.cjs Status
elm make src/Parse.elm --optimize --output=build/parse.js
node run.cjs Parse
""")

Output

$ cd basics
$ elm make src/Records.elm --optimize --output=build/records.js
Dependencies ready!
Success! Compiled 1 module.

    Records ───> build/records.js
$ node run.cjs Records
chai.quantity             2
more.quantity             3
chai.quantity again       2
withTax 5 1200            1260
withFivePercent 1450      1523
List.map withFivePercent  1260, 1523, 1049
pipeline                  6000
$ elm make src/Status.elm --optimize --output=build/status.js
Success! Compiled 1 module.

    Status ───> build/status.js
$ node run.cjs Status
Loading orders...
2 orders, 9150 paise
Could not load orders: timeout
$ elm make src/Parse.elm --optimize --output=build/parse.js
Success! Compiled 1 module.

    Parse ───> build/parse.js
$ node run.cjs Parse
quantity "3"         Ok 3
quantity " 4 "       Ok 4
quantity "0"         Err below 1: 0
quantity "12"        Err above 10: 12
quantity "4e1"       Err not a whole number: "4e1"
quantity ""          Err not a whole number: ""
quantity "2.5"       Err not a whole number: "2.5"
String.toInt "4e1"   Nothing
List.head []         Nothing
List.head [ 5, 7 ]   Just 5
withDefault 1        1

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 basics_session.py

Read the first block. more.quantity is 3, and chai.quantity is still 2 afterwards. withTax 5 1200 is 1260, and withFivePercent 1450 is 1523, because five percent of 1450 paise is 72.5 and the function rounds a half up with (paise * percent + 50) // 100. The last line comes from a pipeline. x |> f means f x, so a chain of pipes reads in the order the data moves instead of from the inside out:

Interactive · stage by stage One pipeline, one value at a time Step through the pipeline of Records.elm and watch the value that each |> hands to the next function.
One pipeline, one value at a time, step 5 of 5. Showing [ chai, more ], List.map lineTotal, List.sum, String.fromInt, The report line. In focus: The report line.[ chai, more ]two lines of an orderList.map lineTotal[ 2400, 3600 ]List.sum6000String.fromInt"6000"The report linepipeline 6000

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. Start with a value. The pipeline starts from the list [ chai, more ]: chai is 2 cups at 1200 paise, and more is the same record with quantity 3.
  2. The first pipe. x |> f means f x. The list becomes the last argument of List.map lineTotal, which multiplies paise by quantity in each record: [ 2400, 3600 ].
  3. Add them up. The next |> hands [ 2400, 3600 ] to List.sum, which returns 6000. Each step gets the value of the step before it as its last argument.
  4. Turn it into text. String.fromInt turns the Int 6000 into the String "6000". Elm never turns a number into text by itself, which the next section of errors shows.
  5. Read top to bottom. Written without pipes, the same value is String.fromInt (List.sum (List.map lineTotal [ chai, more ])), read from the inside out. The pipeline reads in the order the data moves.

Read a type annotation from the left

withTax : Int -> Int -> Int is Int -> (Int -> Int). Give a function fewer arguments than its type lists and you get a function back, which is how List.map (withTax 5) works without a wrapper such as (p) => withTax(5, p).

Checkpoint

Check: Records, curried functions and pipes

  1. Question 1 of 2 Records.elm makes more = { chai | quantity = 3 }. What is chai.quantity afterwards?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 2, because a record update makes a new record and leaves chai as it was

    An Elm value never changes. { chai | quantity = 3 } is a new record with one field different, so every other name for the old record still sees 2.

  2. Question 2 of 2 withTax : Int -> Int -> Int adds percent tax to paise, rounding a half up. What is List.map (withTax 5) [ 1200, 999 ]?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: [ 1260, 1049 ]

    withTax 5 returns a function Int -> Int, which List.map calls on each amount. The session printed 1260, 1523, 1049 for 1200, 1450 and 999.

A custom type lists every state, and case must handle each one

A screen that loads orders is loading, has loaded them, or has failed. In JavaScript that is often three fields, loading, error and data, whose 8 combinations include states that mean nothing:

loading error data What it means
true null null loading
false null a list loaded
false a message null failed
any of the other 5 no screen knows how to draw it

Elm writes the same thing as one custom type (Elm): An Elm type declared with type and a list of variants, such as Loading, Loaded (List Order) and Failed String. A value is exactly one variant, and a case over the type must handle every variant or the module does not compile. with three variants, and only those three values exist. A case expression takes a Status apart, and each branch gets the data its variant carries:

Status.elm: three states, one type, one case ELM · basics/src/Status.elm
port module Status exposing (main)

{-| One custom type for the three states of a list of orders, and a case
expression that takes it apart.
-}


port out : String -> Cmd msg


type alias Order =
    { id : String
    , paise : Int
    }


type Status
    = Loading
    | Loaded (List Order)
    | Failed String


describe : Status -> String
describe status =
    case status of
        Loading ->
            "Loading orders..."

        Loaded orders ->
            String.fromInt (List.length orders)
                ++ " orders, "
                ++ String.fromInt (List.sum (List.map .paise orders))
                ++ " paise"

        Failed reason ->
            "Could not load orders: " ++ reason


main : Program () () Never
main =
    Platform.worker
        { init =
            \_ ->
                ( ()
                , out
                    (String.join "\n"
                        (List.map describe
                            [ Loading
                            , Loaded [ { id = "A-17", paise = 6750 }, { id = "A-18", paise = 2400 } ]
                            , Failed "timeout"
                            ]
                        )
                    )
                )
        , update = \_ model -> ( model, Cmd.none )
        , subscriptions = \_ -> Sub.none
        }

The session printed one line per variant: Loading orders..., 2 orders, 9150 paise and Could not load orders: timeout. Now add a fourth state, Refreshing (List Order), to show old orders while new ones load, as broken/src/Status.elm does without touching its case, and build it:

A fourth variant that the case does not handle yet Python · variant_session.py
"""Builds broken/src/Status.elm, whose Status type has a fourth variant, Refreshing, that its case expression does not
handle yet (session.py plays it)."""

from session import cache, run

cache()
run("""
cd broken
! elm make src/Status.elm --output=/dev/null
""")

Output

$ cd broken
$ elm make src/Status.elm --output=/dev/null
Dependencies ready!
Compiling ...-- MISSING PATTERNS --------------------------------------------- src/Status.elm

This `case` does not have branches for all possibilities:

23|>    case status of
24|>        Loading ->
25|>            "Loading orders..."
26|>
27|>        Loaded orders ->
28|>            String.fromInt (List.length orders) ++ " orders"
29|>
30|>        Failed reason ->
31|>            "Could not load orders: " ++ reason

Missing possibilities include:

    Refreshing _

I would have to crash if I saw one of those. Add branches for them!

Hint: If you want to write the code for each branch later, use `Debug.todo` as a
placeholder. Read <https://elm-lang.org/0.19.3/missing-patterns> for more
guidance on this workflow.

Detected problems in 1 module.
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 variant_session.py

The build stopped with MISSING PATTERNS, quoted the whole case with its line numbers and listed Refreshing _ under Missing possibilities include. That is the The compiler's check that the branches of a case expression cover every value of the type. In Elm a case that misses a variant does not compile, and the error lists the missing patterns.: every case over Status without a branch for the new variant is a compile error, so a new state becomes a list of places to update, not a blank screen in production. The hint suggests Debug.todo as a placeholder while you write the branches, which an optimised build refuses, as the last section shows.

A catch-all branch that hides the next state

Ending a case with _ -> "Unknown" turns the missing-pattern error off: Refreshing would be shown as “Unknown” with no warning. Name every variant, so the compiler can find each case that must learn about a new one.

Checkpoint

Check: Custom types and case

  1. Question 1 of 2 Why is type Status = Loading | Loaded (List Order) | Failed String safer than an object with loading, error and data fields?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Only the three meaningful states exist, so a combination such as loading with data and an error cannot be written

    A custom type lists every state a value can be in, each with only the data that state has, so the impossible mixes of flags and nulls are ruled out by the type.

  2. Question 2 of 2 You add Refreshing (List Order) to Status and change nothing else. What happens when you build?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The build stops with MISSING PATTERNS at every case over Status that has no Refreshing branch

    The exhaustiveness check turns a new variant into a list of compile errors, one for each case that must learn about it, so no state is forgotten.

Maybe and Result put absence and failure into the type

Where JavaScript returns undefined or null, Elm returns a Maybe: Elm's type for a value that may be missing: Just a value, or Nothing. It takes the place of null and undefined, and code must say what happens in the Nothing case before it can use the value.: Just a value or Nothing. List.head [] is Nothing and List.head [ 5, 7 ] is Just 5, and a Maybe Int cannot be used as an Int until the code says what happens in the Nothing case, with case or with Maybe.withDefault. Where JavaScript throws, Elm returns a Result (Elm): Elm's type for an operation that can fail: Ok with a value, or Err with a reason, as in Result String Int. It takes the place of exceptions for failures a program expects, such as text that is not a number.: Ok a value or Err a reason.

quantity in Parse.elm reads a quantity typed into a form. It trims the text, asks String.toInt for a whole number, turns Nothing into an Err with a message, and then checks the range with Result.andThen:

Parse.elm: a quantity from 1 to 10, or the reason it is not one ELM · basics/src/Parse.elm
port module Parse exposing (main)

{-| Maybe and Result instead of null and exceptions: reading a quantity typed
into a form.
-}


port out : String -> Cmd msg


{-| A quantity from 1 to 10, or the reason the text is not one.
-}
quantity : String -> Result String Int
quantity text =
    String.toInt (String.trim text)
        |> Result.fromMaybe ("not a whole number: \"" ++ text ++ "\"")
        |> Result.andThen inRange


inRange : Int -> Result String Int
inRange n =
    if n < 1 then
        Err ("below 1: " ++ String.fromInt n)

    else if n > 10 then
        Err ("above 10: " ++ String.fromInt n)

    else
        Ok n


show : Result String Int -> String
show result =
    case result of
        Ok n ->
            "Ok " ++ String.fromInt n

        Err reason ->
            "Err " ++ reason


showMaybe : Maybe Int -> String
showMaybe maybe =
    case maybe of
        Just n ->
            "Just " ++ String.fromInt n

        Nothing ->
            "Nothing"


lines : List String
lines =
    List.map (\text -> String.padRight 21 ' ' ("quantity \"" ++ text ++ "\"") ++ show (quantity text))
        [ "3", " 4 ", "0", "12", "4e1", "", "2.5" ]
        ++ [ "String.toInt \"4e1\"   " ++ showMaybe (String.toInt "4e1")
           , "List.head []         " ++ showMaybe (List.head [])
           , "List.head [ 5, 7 ]   " ++ showMaybe (List.head [ 5, 7 ])
           , "withDefault 1        " ++ String.fromInt (Maybe.withDefault 1 (String.toInt "x"))
           ]


main : Program () () Never
main =
    Platform.worker
        { init = \_ -> ( (), out (String.join "\n" lines) )
        , update = \_ model -> ( model, Cmd.none )
        , subscriptions = \_ -> Sub.none
        }

The session’s third block shows each input. " 4 " gave Ok 4, because the function trims first. "4e1", which JavaScript’s Number reads as 40, gave Err not a whole number: "4e1": String.toInt accepts digits with an optional sign and nothing else. "0" and "12" are whole numbers, and inRange turned them into Err below 1: 0 and Err above 10: 12. Follow three of them through the chain:

Interactive · stage by stage How quantity turns text into a number, or a reason Step through quantity with three inputs from the session, and see where each one leaves the chain.
How quantity turns text into a number, or a reason, step 6 of 6. Showing The text typed, String.trim, String.toInt, Result.fromMaybe, Result.andThen inRange, Ok n. In focus: Ok n.Err: not a whole numberErr: above 10The text typed" 4 ", "4e1" or "12"String.trimspaces at both ends goString.toIntJust n or NothingResult.fromMaybeNothing becomes an ErrResult.andThen inRange1 to 10, or an ErrOk na quantity you can use

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Trim first. String.toInt refuses " 4 " because of the spaces, so quantity trims the text first and passes on "4".
  2. Maybe a number. String.toInt "4" is Just 4. For "4e1" it is Nothing: Elm reads digits with an optional sign, not the exponent that JavaScript's Number accepts.
  3. A reason instead of Nothing. Result.fromMaybe turns Just 4 into Ok 4 and Nothing into Err with the message. From here the value carries the reason it failed.
  4. Nothing skips the rest. An Err passes through andThen untouched, so "4e1" leaves the chain as Err not a whole number: "4e1", and inRange never runs for it. A form would show the reason and let the person type again.
  5. The range check. Result.andThen inRange runs only on an Ok. For "12" it finds 12 above 10 and returns Err above 10: 12.
  6. The happy path. For " 4 " every step succeeds, and quantity returns Ok 4. The caller must take the value out with case, as show does, so the failure cases cannot be forgotten.

Result.andThen is what keeps the chain flat: it runs the next step only on an Ok, and passes an Err through untouched, so the first failure is the one reported.

Key idea

A missing value is a Maybe and an expected failure is a Result; the type tells every caller to handle both cases, and Result.andThen chains the steps so that the first Err is the answer.

Checkpoint

Check: Maybe and Result

  1. Question 1 of 2 What does String.toInt "4e1" return in Elm, where Number("4e1") is 40 in JavaScript?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nothing, because String.toInt accepts digits with an optional sign and nothing else

    String.toInt returns a Maybe Int: Just n for text made of digits with an optional + or -, and Nothing for anything else, which the caller must handle.

  2. Question 2 of 2 quantity is String.toInt (String.trim text) |> Result.fromMaybe msg |> Result.andThen inRange. What does quantity "4e1" return, and does inRange run?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Err not a whole number: "4e1", and inRange never runs

    The first failure is the answer: andThen skips the rest of the chain on an Err, so each step can assume the steps before it worked.

Numbers divide as in JavaScript, with three surprises

An Elm Int is a JavaScript number, and most arithmetic is JavaScript’s own, but three operations have rules of their own. The Numbers worker prints one expression a line, and compare.mjs prints the matching JavaScript:

Numbers.elm: division, remainders, rounding and large values ELM · basics/src/Numbers.elm
port module Numbers exposing (main)

{-| How Elm divides, rounds and stores numbers, one expression a line.
-}


port out : String -> Cmd msg


row : String -> String -> String
row expression value =
    String.padRight 22 ' ' expression ++ value


lines : List String
lines =
    [ row "7 // 2" (String.fromInt (7 // 2))
    , row "-7 // 2" (String.fromInt (-7 // 2))
    , row "modBy 2 -7" (String.fromInt (modBy 2 -7))
    , row "remainderBy 2 -7" (String.fromInt (remainderBy 2 -7))
    , row "modBy -3 7" (String.fromInt (modBy -3 7))
    , row "7 // 0" (String.fromInt (7 // 0))
    , row "remainderBy 0 7" (String.fromInt (remainderBy 0 7))
    , row "round 2.5" (String.fromInt (round 2.5))
    , row "round -2.5" (String.fromInt (round -2.5))
    , row "truncate -2.7" (String.fromInt (truncate -2.7))
    , row "floor -2.7" (String.fromInt (floor -2.7))
    , row "2147483647 // 1" (String.fromInt (2147483647 // 1))
    , row "2147483648 // 1" (String.fromInt (2147483648 // 1))
    , row "3000000000 // 100" (String.fromInt (3000000000 // 100))
    , row "truncate 3.0e9" (String.fromInt (truncate 3.0e9))
    , row "floor 3.0e9" (String.fromInt (floor 3.0e9))
    , row "2 ^ 53 + 1" (String.fromInt (2 ^ 53 + 1))
    , row "0.1 + 0.2" (String.fromFloat (0.1 + 0.2))
    , row "toFloat 7 / 2" (String.fromFloat (toFloat 7 / 2))
    ]


main : Program () () Never
main =
    Platform.worker
        { init = \_ -> ( (), out (String.join "\n" lines) )
        , update = \_ model -> ( model, Cmd.none )
        , subscriptions = \_ -> Sub.none
        }
The table, a refund, and modBy 0, built and run Python · numbers_session.py
"""Builds and runs the number workers of basics/: the table of Numbers, the sign of a refund in Refund, and modBy 0 in
ModZero, once in development mode and once optimised (session.py plays it)."""

from session import cache, run

cache()
run("""
cd basics
elm make src/Numbers.elm --optimize --output=build/numbers.js
node run.cjs Numbers
elm make src/Refund.elm --optimize --output=build/refund.js
node run.cjs Refund
elm make src/ModZero.elm --output=build/modzero.js
! node run.cjs ModZero
elm make src/ModZero.elm --optimize --output=build/modzero.js
! node run.cjs ModZero
""")

Output

$ cd basics
$ elm make src/Numbers.elm --optimize --output=build/numbers.js
Dependencies ready!
Success! Compiled 1 module.

    Numbers ───> build/numbers.js
$ node run.cjs Numbers
7 // 2                3
-7 // 2               -3
modBy 2 -7            1
remainderBy 2 -7      -1
modBy -3 7            -2
7 // 0                0
remainderBy 0 7       NaN
round 2.5             3
round -2.5            -2
truncate -2.7         -2
floor -2.7            -3
2147483647 // 1       2147483647
2147483648 // 1       -2147483648
3000000000 // 100     30000000
truncate 3.0e9        -1294967296
floor 3.0e9           3000000000
2 ^ 53 + 1            9007199254740992
0.1 + 0.2             0.30000000000000004
toFloat 7 / 2         3.5
$ elm make src/Refund.elm --optimize --output=build/refund.js
Success! Compiled 1 module.

    Refund ───> build/refund.js
$ node run.cjs Refund
paise   rupees    signedRupees
   150   ₹1.50     ₹1.50
  -150   ₹-1.50    -₹1.50
    -5   ₹0.95     -₹0.05
  -199   ₹-1.01    -₹1.99
$ elm make src/ModZero.elm --output=build/modzero.js
Success! Compiled 1 module.

    ModZero ───> build/modzero.js
$ node run.cjs ModZero
Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.3/optimize for better performance and smaller assets.
ModZero stopped: Cannot perform mod 0. Division by zero error.
[exit status 1]
$ elm make src/ModZero.elm --optimize --output=build/modzero.js
Success!

    ModZero ───> build/modzero.js
$ node run.cjs ModZero
ModZero stopped: https://github.com/elm/core/blob/1.0.0/hints/11.md
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 numbers_session.py

compare.mjs: the same arithmetic in JavaScript JavaScript · compare/compare.mjs
// The JavaScript expressions that match the lines of Numbers.elm, as Node.js prints them.
const rows = [
  ['Math.trunc(7 / 2)', Math.trunc(7 / 2)],
  ['Math.trunc(-7 / 2)', Math.trunc(-7 / 2)],
  ['-7 % 2', -7 % 2],
  ['7 % -3', 7 % -3],
  ['7 / 0', 7 / 0],
  ['7 % 0', 7 % 0],
  ['Math.round(2.5)', Math.round(2.5)],
  ['Math.round(-2.5)', Math.round(-2.5)],
  ['Math.trunc(-2.7)', Math.trunc(-2.7)],
  ['2147483648 | 0', 2147483648 | 0],
  ['Math.trunc(3e9)', Math.trunc(3e9)],
  ['2 ** 53 + 1', 2 ** 53 + 1],
  ['0.1 + 0.2', 0.1 + 0.2],
  ['Number("4e1")', Number('4e1')],
  ['parseInt("4e1", 10)', parseInt('4e1', 10)],
];

for (const [expression, value] of rows) console.log(expression.padEnd(22) + String(value));

Output

Math.trunc(7 / 2)     3
Math.trunc(-7 / 2)    -3
-7 % 2                -1
7 % -3                1
7 / 0                 Infinity
7 % 0                 NaN
Math.round(2.5)       3
Math.round(-2.5)      -2
Math.trunc(-2.7)      -2
2147483648 | 0        -2147483648
Math.trunc(3e9)       3000000000
2 ** 53 + 1           9007199254740992
0.1 + 0.2             0.30000000000000004
Number("4e1")         40
parseInt("4e1", 10)   4

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node compare.mjs

Expression Elm 0.19.3 JavaScript, Node.js 24.21.0
−7 divided by 2 -7 // 2 is −3 Math.trunc(-7 / 2) is −3
remainder of −7 by 2 modBy 2 -7 is 1, remainderBy 2 -7 is −1 -7 % 2 is −1
7 divided by 0 7 // 0 is 0, remainderBy 0 7 is NaN 7 / 0 is Infinity, 7 % 0 is NaN
−2.5 rounded round -2.5 is −2 Math.round(-2.5) is −2
2147483648 divided by 1 2147483648 // 1 is −2147483648 2147483648 | 0 is −2147483648
3.0e9 without its fraction truncate 3.0e9 is −1294967296 Math.trunc(3e9) is 3000000000
2^53 + 1 9007199254740992 9007199254740992

The first surprise is the remainder. Elm has two: remainderBy keeps the sign of the number divided, like JavaScript’s %, and modBy keeps the sign of the modulus, so modBy 2 -7 is 1. That is floor division: Integer division that rounds the quotient down, towards minus infinity, so -7 divided by 2 gives -4. Tcl's / on integers and Python's // divide this way, and their remainder takes the divisor's sign. More in “A one-week plan for learning any language” arithmetic, while // drops the fraction towards zero, which is truncating division: Integer division that drops the fraction, rounding towards zero, so -7 divided by 2 gives -3. C, Objective-C and Visual Basic's \ divide this way, and their remainder takes the dividend's sign. More in “A one-week plan for learning any language”. The second is size. elm/core’s source computes // as (a / b) | 0 and truncate as n | 0, and JavaScript’s | works on 32-bit integers, so a result past 2,147,483,647 wraps round: truncate 3.0e9 is −1294967296, while floor 3.0e9 gives 3000000000. The third is zero: 7 // 0 is 0 and remainderBy 0 7 is an Int that prints NaN, but modBy 0 7 stops the program, with Cannot perform mod 0. Division by zero error. in development and only a link to a hint in the optimised build. Rounding holds no surprise for a JavaScript developer: round is Math.round, so a half always goes up, towards positive infinity, and round 2.5 is 3 while round -2.5 is −2.

IEEE 754 Floating-Point Converter Enter 9007199254740993 to see the 64-bit float an Elm Int becomes: 9007199254740992.

The session also ran two smaller workers. ModZero.elm computes modBy 0 7, which no type rules out, and stopped both times, as described above. Refund.elm prints amounts of paise with the rupees function of the Orders worker, and beside it a version that handles the sign first:

Refund.elm: rupees and signedRupees, given negative amounts ELM · basics/src/Refund.elm
port module Refund exposing (main)

{-| The rupees function of the Orders worker, given negative amounts, and a
version that handles the sign first.
-}


port out : String -> Cmd msg


rupees : Int -> String
rupees paise =
    "₹" ++ String.fromInt (paise // 100) ++ "." ++ String.padLeft 2 '0' (String.fromInt (modBy 100 paise))


signedRupees : Int -> String
signedRupees paise =
    if paise < 0 then
        "-" ++ rupees (abs paise)

    else
        rupees paise


lines : List String
lines =
    List.map
        (\paise -> String.padLeft 6 ' ' (String.fromInt paise) ++ "   " ++ String.padRight 10 ' ' (rupees paise) ++ signedRupees paise)
        [ 150, -150, -5, -199 ]


main : Program () () Never
main =
    Platform.worker
        { init = \_ -> ( (), out ("paise   rupees    signedRupees\n" ++ String.join "\n" lines) )
        , update = \_ model -> ( model, Cmd.none )
        , subscriptions = \_ -> Sub.none
        }

Mixing // and modBy for money

Orders.elm printed rupees as paise // 100 and modBy 100 paise. For a refund of −5 paise that is 0 rupees and 95 paise, so the Refund worker printed ₹0.95, and −199 paise came out as ₹-1.01. Take the sign first and format abs paise, as signedRupees does: -₹0.05 and -₹1.99.

Checkpoint

Check: How Elm divides and rounds

  1. Question 1 of 2 In Elm, what are modBy 2 -7 and remainderBy 2 -7?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 1 and -1

    Elm has two remainders: modBy, floored, which is never negative for a positive modulus, and remainderBy, which matches JavaScript's %. The Numbers table printed 1 and -1.

  2. Question 2 of 2 An Elm app stores a file size of 3,000,000,000 bytes as a Float and calls truncate on it. What does it get?

    Choose one answer.

    A hint

    elm/core computes truncate as n | 0.

    Show the answer: Question 2 of 2

    Answer: -1294967296, because truncate goes through a 32-bit integer

    // and truncate are computed with JavaScript's | 0, which works on 32-bit integers, so results past 2,147,483,647 wrap. Use floor, ceiling or round, which use Math functions, for large values.

Compiler errors explain themselves, and the exit status tells the truth

The last session builds the other two modules of broken/. Receipt.elm appends an Int to a String, which JavaScript would turn into text without a word:

Receipt.elm: a number where a string should be ELM · broken/src/Receipt.elm
module Receipt exposing (line)

{-| A line of a receipt, with a mistake JavaScript would accept.
-}


line : String -> Int -> String
line item paise =
    item ++ ": " ++ paise ++ " paise"
A type mismatch, then a Debug.log in an optimised build Python · errors_session.py
"""Two builds of broken/ that stop: a type mismatch, and an optimised build with a Debug.log left in, which fails while
the file of an earlier development build stays in place (session.py plays it)."""

from session import cache, run

cache()
run("""
cd broken
! elm make src/Receipt.elm --output=/dev/null
elm make src/Logged.elm --output=build/logged.js
node run.cjs Logged
! elm make src/Logged.elm --optimize --output=build/logged.js
grep -c "Compiled in DEV mode" build/logged.js
""")

Output

$ cd broken
$ elm make src/Receipt.elm --output=/dev/null
Dependencies ready!
Compiling ...-- TYPE MISMATCH ----------------------------------------------- src/Receipt.elm

The (++) operator can append List and String values, but not Int values like
this:

9|     item ++ ": " ++ paise ++ " paise"
                       ^^^^^
Try using String.fromInt to turn it into a string? Or put it in [] to make it a
list? Or switch to the (::) operator?

Detected problems in 1 module.
[exit status 1]
$ elm make src/Logged.elm --output=build/logged.js
Success! Compiled 1 module.

    Logged ───> build/logged.js
$ node run.cjs Logged
Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.3/optimize for better performance and smaller assets.
total: 2650
total paise: 2650
$ elm make src/Logged.elm --optimize --output=build/logged.js
Compiling ...-- DEBUG REMNANTS --------------------------------------------------------------

There are uses of the `Debug` module in the following modules:

    Logged

But the --optimize flag only works if all `Debug` functions are removed!

Note: The issue is that --optimize strips out info needed by `Debug` functions.
Here are two examples:

    (1) It shortens record field names. This makes the generated JavaScript
    smaller, but `Debug.toString` cannot know the real field names anymore.

    (2) Values like `type Height = Height Float` are unboxed. This reduces
    allocation, but it also means that `Debug.toString` cannot tell if it is
    looking at a `Height` or `Float` value.

There are a few other cases like that, and it will be much worse once we start
inlining code. That optimization could move `Debug.log` and `Debug.todo` calls,
resulting in unpredictable behavior. I hope that clarifies why this restriction
exists!

Success!
[exit status 1]
$ grep -c "Compiled in DEV mode" build/logged.js
1

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py

The first error names the operator and the line, points at paise with carets and suggests the fix first: Try using String.fromInt to turn it into a string?. The second is subtler. Logged.elm keeps a Debug.log from a debugging session; the development build printed total: 2650 and worked. The optimised build printed DEBUG REMNANTS, because --optimize renames record fields and unboxes values that Debug would need, and its last line on the screen was Success!, yet its exit status was 1. And build/logged.js was still there: grep found the development build’s Compiled in DEV mode inside it. A deploy script that reads the screen, or that does not stop on a failed step, uploads that old file. Step through what a script must check:

Interactive · stage by stage What a build script must read after elm make Step through the optimised build of Logged.elm, the one that failed, and see what a script should check.
What a build script must read after elm make, step 6 of 6. Showing elm make --optimize, The terminal, The exit status, build/logged.js, Minify and deploy. In focus: Minify and deploy.not 0: fix and rebuildelm make --optimizesrc/Logged.elmThe terminalends with Success!The exit status1, the build failedbuild/logged.jsstill the earlier buildMinify and deployonly on exit status 0

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The build runs. elm make --optimize finds Debug.log in Logged and prints DEBUG REMNANTS with the reason: --optimize strips what Debug needs.
  2. What the screen says. The progress line still ends with Success!, the word a person or a script looking at the text would see last.
  3. What the status says. The exit status is 1, and that is the answer to trust: the optimised build did not happen.
  4. The file that is left. build/logged.js is the file the earlier development build wrote, with its DEV mode warning inside, as grep -c found. A script that uploads it ships the wrong build.
  5. Stop on failure. So the script stops when the status is not 0, for example with set -e, or with elm make … && the next step; then the Debug.log is removed and the build runs again.
  6. Ship only a good build. Only after exit status 0 does the script minify and deploy, and it builds into a clean folder, so an old file can never pass for a new one.

Let the exit status stop the script

Start a deploy script with set -e, or join its steps with &&, so that elm make --optimize returning 1 ends it; and build into a fresh folder, so a file from an earlier build can never be shipped as the new one.

Checkpoint

Check: Compiler errors and the exit status

  1. Question 1 of 2 item ++ ": " ++ paise ++ " paise" fails with TYPE MISMATCH because paise is an Int. What does the compiler suggest?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: String.fromInt paise, to turn the number into a string

    ++ appends values of the same type. The compiler names the operator, the line and the value, and suggests String.fromInt, the function that turns an Int into a String.

  2. Question 2 of 2 elm make --optimize printed DEBUG REMNANTS, and its last line on the screen was Success!. What should a deploy script conclude?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The build failed: its exit status was 1, and build/logged.js is still the old development build

    Trust the exit status, not the words on the screen: set -e or && stops the script on 1, and building into a fresh folder keeps an old file from being shipped.

Practise: port two money functions

Both steps are JavaScript, so their sample tests run right here. Step one turns Elm’s integer operators into JavaScript functions that you can hold a port against, line by line: the tests are the rows of the table, the 32-bit wrap and modBy 0 included. The second fixes the refund bug of Orders.elm in a function that must work for any whole number of paise, and its tests are the four amounts of the Refund worker and a few more. Start with the step that surprised you more:

Code lab · 2 steps

Port Elm's arithmetic, then fix refunds

Write //, modBy, remainderBy and round as Elm computes them, then print negative amounts of paise with their sign.

  1. Divide and round the way Elm does To do
  2. Print refunds without losing the sign To do

Exercise · Medium · JavaScript

Divide and round the way Elm does

A port from JavaScript to Elm is safe only if you know what each operator gives. Write four functions in intops.mjs that return exactly what Elm 0.19.3 returned in the lesson's Numbers table, so that you could check a port line by line:

  • idiv(a, b) for Elm's a // b: the quotient without its fraction, which wraps like Elm's when it does not fit in 32 bits (2147483648 // 1 is -2147483648), and is 0 when b is 0.
  • modBy(m, x) for Elm's modBy m x: the remainder with the sign of m (modBy 2 -7 is 1, modBy -3 7 is -2). With m equal to 0 it throws an Error, as Elm's program stopped.
  • remainderBy(m, x) for Elm's remainderBy m x: the remainder with the sign of x (remainderBy 2 -7 is -1).
  • round(x) for Elm's round x: halves go up, towards positive infinity (round -2.5 is -2).

The order of the arguments follows Elm: the modulus comes first. The starter code is what a JavaScript developer writes from habit; the sample tests show where each function differs.

Starter code · intops.mjs

/** Elm's a // b. */
export function idiv(a, b) {
  // Replace this: Math.floor rounds -3.5 down to -4, and Elm does not.
  return Math.floor(a / b);
}

/** Elm's modBy m x: the remainder with the sign of the modulus. */
export function modBy(m, x) {
  // Replace this: % keeps the sign of x.
  return x % m;
}

/** Elm's remainderBy m x: the remainder with the sign of x. */
export function remainderBy(m, x) {
  return x % m;
}

/** Elm's round x. */
export function round(x) {
  // Replace this: Math.trunc drops the fraction instead of rounding.
  return Math.trunc(x);
}
The sample tests · intops.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { idiv, modBy, remainderBy, round } from './intops.mjs';

test('// drops the fraction, towards zero', () => {
  assert.equal(idiv(7, 2), 3);
  assert.equal(idiv(-7, 2), -3);
  assert.equal(idiv(7, -2), -3);
  assert.equal(idiv(-7, -2), 3);
});

test('// gives 0 for a divisor of 0, and wraps past 32 bits', () => {
  assert.equal(idiv(7, 0), 0);
  assert.equal(idiv(2147483647, 1), 2147483647);
  assert.equal(idiv(2147483648, 1), -2147483648);
  assert.equal(idiv(3000000000, 100), 30000000);
});

test('modBy takes the sign of the modulus', () => {
  assert.equal(modBy(2, -7), 1);
  assert.equal(modBy(3, 7), 1);
  assert.equal(modBy(-3, 7), -2);
  assert.equal(modBy(4, -5), 3);
  assert.equal(modBy(100, -5), 95);
  assert.equal(modBy(5, 10), 0);
  assert.ok(modBy(5, -10) === 0, 'modBy 5 -10 is 0 (JavaScript may give -0, which is equal to 0)');
});

test('modBy 0 stops with an error', () => {
  assert.throws(() => modBy(0, 7), Error);
});

test('remainderBy takes the sign of the number divided', () => {
  assert.equal(remainderBy(2, -7), -1);
  assert.equal(remainderBy(4, -5), -1);
  assert.equal(remainderBy(-3, 7), 1);
});

test('round sends halves towards positive infinity', () => {
  assert.equal(round(2.5), 3);
  assert.equal(round(-2.5), -2);
  assert.equal(round(-1.5), -1);
  assert.equal(round(1.2), 1);
  assert.equal(round(-1.8), -2);
});
A hint

Elm's // divides and then drops the fraction through a 32-bit integer, which in JavaScript is (a / b) | 0. remainderBy is JavaScript's own %. For modBy, start from x % m and add m when the result is not 0 and its sign differs from the sign of m.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Exercise · Easy · JavaScript

Print refunds without losing the sign

The starter code is the rupees function of the Orders worker, ported to JavaScript as it behaves in Elm: the rupees with Elm's //, which drops the fraction towards zero, and the paise with Elm's modBy 100, which is never negative. For 150 paise it prints "₹1.50", but for a refund of -5 paise it prints "₹0.95", as the Refund worker did.

Change signedRupees(paise) in signed.mjs so that it works for any whole number of paise: a minus sign before the rupee sign when the amount is below zero, then the amount without its sign, with two digits of paise. -5 paise is "-₹0.05", -199 paise is "-₹1.99", and 0 is "₹0.00", without a sign.

Starter code · signed.mjs

/** Elm's modBy 100: never negative. */
const modBy100 = (x) => ((x % 100) + 100) % 100;

/** Any whole number of paise as rupees, with a minus sign for a refund: -5 is "-₹0.05". */
export function signedRupees(paise) {
  // Replace this: it is Orders.elm's rupees, which mixes a quotient towards zero with a remainder that is never negative.
  return '₹' + Math.trunc(paise / 100) + '.' + String(modBy100(paise)).padStart(2, '0');
}
The sample tests · signed.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { signedRupees } from './signed.mjs';

test('amounts of 0 or more are unchanged', () => {
  assert.equal(signedRupees(150), '₹1.50');
  assert.equal(signedRupees(6750), '₹67.50');
  assert.equal(signedRupees(5), '₹0.05');
  assert.equal(signedRupees(0), '₹0.00');
});

test('the four amounts of the Refund worker', () => {
  assert.equal(signedRupees(150), '₹1.50');
  assert.equal(signedRupees(-150), '-₹1.50');
  assert.equal(signedRupees(-5), '-₹0.05');
  assert.equal(signedRupees(-199), '-₹1.99');
});

test('whole rupees and large refunds', () => {
  assert.equal(signedRupees(-100), '-₹1.00');
  assert.equal(signedRupees(-123456), '-₹1234.56');
  assert.equal(signedRupees(-1), '-₹0.01');
});
A hint

Take the sign out first and format the absolute value, as signedRupees in Refund.elm does with abs paise. Once the number is 0 or more, dropping the fraction and taking the remainder agree, so the split into rupees and paise is right.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then the self-check, from records to compiler errors. It starts with the idea you know least, and every answer you pick is explained at once:

Practice that adapts to you

Practise this lesson

12 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 How do you read the type annotation withTax : Int -> Int -> Int?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: A function that takes an Int and returns a function from Int to Int

    Every Elm function takes one argument at a time, which is why withTax 5 is a function that List.map can use.

  2. Question 2 of 12 chai is 2 cups at 1200 paise and more is chai with quantity 3. What does this expression give?

    Read the code, then choose one answer.

    [ chai, more ] |> List.map lineTotal |> List.sum |> String.fromInt
    Show the answer: Question 2 of 12

    Answer: "6000"

    Each |> passes the value on its left as the last argument of the function on its right. The session printed the line pipeline as 6000.

  3. Question 3 of 12 In describe, the branch Loaded orders -> … names orders. What is orders?

    Choose one answer.

    Show the answer: Question 3 of 12

    Answer: The List Order that this Loaded value carries

    Each variant carries its own data, and case gives it a name in the branch that matches, which is why Failed reason can use reason and Loading has nothing to name.

  4. Question 4 of 12 A teammate ends every case over Status with _ -> "Unknown" "to be safe". What does that cost when the next variant is added?

    Choose one answer.

    Show the answer: Question 4 of 12

    Answer: The compiler can no longer list the cases to update: the new state is silently shown as Unknown

    Name every variant in a case over a custom type. The exhaustiveness check then turns each new variant into a compile error at each place that must handle it.

  5. Question 5 of 12 What is Maybe.withDefault 1 (String.toInt "x")?

    Choose one answer.

    Show the answer: Question 5 of 12

    Answer: 1, because String.toInt "x" is Nothing

    Maybe.withDefault is the shortest way out of a Maybe when a default makes sense. When it does not, use case and handle Nothing explicitly.

  6. Question 6 of 12 A JavaScript function returns the first order or undefined, and callers sometimes forget the undefined. You port it to Elm. Which signature keeps that bug from coming back?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: firstOrder : List Order -> Maybe Order

    Put absence in the type: Maybe Order says the order may be missing, and the compiler makes each caller decide what happens then, as List.head does.

  7. Question 7 of 12 What is -7 // 2 in Elm?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: -3, because // drops the fraction towards zero

    Elm's // is truncating division, like Math.trunc in JavaScript. modBy is the floored one, which is why mixing the two broke negative amounts.

  8. Question 8 of 12 What do round 2.5 and round -2.5 give in Elm?

    Choose one answer.

    Show the answer: Question 8 of 12

    Answer: 3 and -2

    Elm's round is JavaScript's Math.round, so a value exactly halfway always goes to the larger whole number: 2.5 becomes 3 and -2.5 becomes -2. Use truncate or floor when the rule must be different, and test the halves.

  9. Question 9 of 12 rupees paise = "₹" ++ String.fromInt (paise // 100) ++ "." ++ String.padLeft 2 '0' (String.fromInt (modBy 100 paise)). What does rupees -5 print?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: ₹0.95

    // truncates towards zero and modBy floors, so for negative amounts the two parts do not belong together. Format abs paise and put the sign in front.

  10. Question 10 of 12 An Elm report adds up 2,500,000,000 paise of sales and shows the total in rupees with total // 100. Is that safe?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: Yes for this total: the quotient, 25,000,000, fits in 32 bits; it wraps only when the result passes 2,147,483,647

    The wrap comes from | 0 on the result. Totals whose rupee value stays below 2,147,483,647 are safe with //; for larger quotients use floor (toFloat total / 100) or keep the amount whole.

  11. Question 11 of 12 Why does elm make --optimize refuse a module that calls Debug.log?

    Choose one answer.

    Show the answer: Question 11 of 12

    Answer: Optimising renames record fields and unboxes values, so Debug would print wrong or misleading values

    --optimize changes the shape of values in the JavaScript it writes, and Debug needs the original shape, so the compiler refuses to optimise while any Debug call remains.

  12. Question 12 of 12 A deploy script runs elm make --optimize --output=dist/app.js; then uploads dist/app.js, without set -e. A Debug.log slips in. What reaches production?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: The previous dist/app.js, because the failed build wrote nothing and the script carried on

    A failed elm make leaves whatever was there before. Stop the script on the exit status and build into a clean folder, so a failure can never ship an old file.

Interview questions

Warm-up (fresher to mid-level): What do Maybe and Result replace in JavaScript, and how do you get a value out of one?

Maybe replaces null and undefined: a value is Just x or Nothing, and functions that may find nothing say so in their type, as List.head and String.toInt do. Result replaces exceptions for failures the program expects: Ok x or Err reason, with the reason’s type in the signature, such as Result String Int. The type forces the caller to handle the other case, so there is no TypeError from a missing value. To get the value out, use case with a branch for each variant, Maybe.withDefault when a default makes sense, or keep working inside with Maybe.map and Result.andThen, which skip the work on Nothing or Err. In the lesson, quantity "4e1" returned Err not a whole number: "4e1" where JavaScript’s Number would have read 40.

Two longer questions, one about a money bug and one about a deploy that shipped the wrong build:

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to senior

    The refunds screen of your Elm app shows a refund of 5 paise as 0.95 rupees, while positive amounts look right and every existing test passes. The formatter uses paise // 100 and modBy 100 paise. Explain the bug, fix it, and say which tests would have caught it.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to staff

    After a release, the browser console of your Elm app shows "Compiled in DEV mode", and a Debug.log line prints customer totals. The CI log of the deploy ends with Success!. What happened in the pipeline, and how do you fix it so that it cannot happen again?

    Model answer, Pro. First step and follow-ups

Flashcards · 8 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What does { chai | quantity = 3 } do in Elm?

    It builds a new record with quantity 3 and the other fields copied, like { ...chai, quantity: 3 }. chai stays as it was, since Elm has no assignment.

  2. withTax : Int -> Int -> Int. What is withTax 5?

    A function that adds five percent to a price: every Elm function is curried, so fewer arguments give a function back, ready for List.map (withTax 5).

  3. Why model a loading screen as one custom type rather than loading, error and data fields?

    Three fields allow 8 combinations, 5 of which no screen can draw. Loading, Loaded (List Order) and Failed String allow exactly the three real states.

  4. A variant is added to a type and a case over it is not updated. What does the compiler do?

    It stops with MISSING PATTERNS, quoting the case and listing the missing variant, so every case that must learn about it is found before the program runs.

  5. What does String.toInt "4e1" return in Elm?

    Nothing: it accepts digits with an optional sign and nothing else, while JavaScript's Number("4e1") gives 40.

  6. What do modBy 2 -7 and remainderBy 2 -7 return, and what does modBy 0 do?

    1 and −1: modBy takes the sign of the modulus, remainderBy that of the number divided, like JavaScript's %. modBy 0 stops the program, while 7 // 0 is simply 0.

  7. Why is truncate 3.0e9 −1294967296 in Elm?

    elm/core computes truncate as n | 0 and // as (a / b) | 0, and JavaScript's | works on 32 bits, so results past 2,147,483,647 wrap. floor 3.0e9 gives 3000000000.

  8. How can a deploy ship an old build after elm make --optimize refuses a Debug.log?

    The screen may still end with Success! while the exit status is 1, and the old file stays. Stop the script on the status, with set -e or &&, and build into a fresh folder.

Key takeaways

  • Elm values never change: a record update makes a new record, every function is curried, and |> passes a value on as the last argument of the next function.
  • A custom type lists every state a value can be in, and adding a variant turns each case that misses it into a compile error, as Refreshing did.
  • Maybe stands for absence and Result for expected failure; String.toInt "4e1" is Nothing, and Result.andThen stops a chain at its first Err.
  • // drops the fraction towards zero, modBy takes the sign of the modulus and remainderBy that of the number, // and truncate wrap past 2,147,483,647, and modBy 0 stops the program.
  • Compiler errors name the line and suggest the fix, and elm make --optimize refuses Debug; a script must stop on its exit status, because the screen may still end with Success! while an old file stays in place.

Terms in this lesson

curried function
A function that takes its arguments one at a time: given the first, it returns a function that waits for the rest. Every Elm function works this way, so withTax 5 is itself a function that adds five percent.
custom type (Elm)
An Elm type declared with type and a list of variants, such as Loading, Loaded (List Order) and Failed String. A value is exactly one variant, and a case over the type must handle every variant or the module does not compile.
Maybe
Elm's type for a value that may be missing: Just a value, or Nothing. It takes the place of null and undefined, and code must say what happens in the Nothing case before it can use the value.
record update
Elm's { record | field = value }, which makes a new record with some fields changed and leaves the original as it was, like { ...record, field: value } in JavaScript. Elm has no assignment to a field.
Result (Elm)
Elm's type for an operation that can fail: Ok with a value, or Err with a reason, as in Result String Int. It takes the place of exceptions for failures a program expects, such as text that is not a number.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.