Files
elm/lessons/02-basics.md
Mark Gerrard 7d7986f3ab Initial commit: Elm tutorial for JavaScript developers
This tutorial includes:
- 7 progressive lessons covering Elm fundamentals
- Exercises with starter code
- Solutions for exercises
- Final project: Task Manager app with localStorage persistence

Topics covered:
- Basic syntax and types
- Functions and functional programming concepts
- The Elm Architecture (Model-View-Update)
- Lists and Maybe types
- HTTP requests and JSON decoding
- Ports for JavaScript interop
2026-03-11 11:07:15 +00:00

8.8 KiB

Lesson 2: Basic Syntax & Types

Learning Goals

By the end of this lesson, you will:

  • Understand Elm's primitive types
  • Write type annotations
  • Work with records (Elm's "objects")
  • Create type aliases
  • Understand union types (a powerful feature JS doesn't have!)

Primitive Types

Numbers

-- Integers
age : Int
age = 30

-- Floating point
price : Float
price = 19.99

-- The 'number' type works with both
double : number -> number
double n = n * 2

double 5      -- 10 : number
double 5.5    -- 11.0 : Float

JavaScript comparison:

// JavaScript - just "number"
const age = 30;
const price = 19.99;

Strings

name : String
name = "Alice"

-- Multi-line strings use triple quotes
poem : String
poem = """
Roses are red,
Violets are blue,
Elm has no nulls,
And neither should you!
"""

-- String operations
String.length "hello"     -- 5
String.reverse "hello"    -- "olleh"
String.toUpper "hello"    -- "HELLO"
"Hello" ++ " World"       -- "Hello World"

JavaScript comparison:

const name = "Alice";
const poem = `
Roses are red...
`;
"Hello" + " World"  // Uses + not ++

Booleans

isActive : Bool
isActive = True

-- Note: True and False are capitalized!
-- Comparison operators
5 > 3      -- True
5 == 5     -- True (equality uses ==, same as JS)
5 /= 3     -- True (not equal uses /= not !==)

-- Boolean operators
True && False   -- False
True || False   -- True
not True        -- False

Key difference: Not-equal is /= in Elm, not !== like JavaScript.

Characters

-- Single characters use single quotes
letter : Char
letter = 'A'

-- Strings use double quotes
word : String
word = "ABC"

JavaScript doesn't have a separate character type.

Type Annotations

Type annotations are optional but highly recommended:

-- Without annotation (Elm infers the type)
add a b = a + b

-- With annotation (clearer and self-documenting)
add : Int -> Int -> Int
add a b = a + b

Reading Type Annotations

greet : String -> String
--      ^input    ^output
greet name = "Hello, " ++ name

add : Int -> Int -> Int
--    ^first  ^second  ^result
add a b = a + b

Think of -> as "then returns". So Int -> Int -> Int reads as: "Takes an Int, then an Int, then returns an Int"

Records: Elm's Objects

Records are like JavaScript objects, but immutable and typed.

-- Defining a record
person =
    { name = "Alice"
    , age = 30
    , email = "alice@example.com"
    }

-- Accessing fields (dot notation, like JS)
person.name    -- "Alice"
person.age     -- 30

-- Or using accessor functions
.name person   -- "Alice"

Record Type Annotations

-- Inline type
alice : { name : String, age : Int }
alice = { name = "Alice", age = 30 }

-- With type alias (preferred for reuse)
type alias Person =
    { name : String
    , age : Int
    , email : String
    }

bob : Person
bob =
    { name = "Bob"
    , age = 25
    , email = "bob@example.com"
    }

Updating Records

In Elm, you can't mutate records. Instead, you create a new one:

-- JavaScript way (mutates)
-- person.age = 31;

-- Elm way (creates new record)
olderPerson = { person | age = 31 }

-- Update multiple fields
updatedPerson =
    { person
        | age = 31
        , email = "newemail@example.com"
    }

The original person is unchanged! This is immutability.

Why Immutability Matters

// JavaScript - Mutation causes bugs
const user = { name: "Alice", score: 100 };
doSomething(user);
console.log(user.score);  // Who knows? doSomething might have changed it!
-- Elm - No surprises
user = { name = "Alice", score = 100 }
newUser = doSomething user
user.score     -- Still 100, guaranteed!
newUser.score  -- Whatever doSomething returned

Type Aliases

Type aliases give names to types:

type alias Person =
    { name : String
    , age : Int
    }

type alias Point =
    { x : Float
    , y : Float
    }

-- Use them in annotations
distance : Point -> Point -> Float
distance p1 p2 =
    sqrt ((p2.x - p1.x)^2 + (p2.y - p1.y)^2)

Type aliases also create constructor functions:

type alias Person =
    { name : String
    , age : Int
    }

-- This automatically creates:
-- Person : String -> Int -> Person

alice = Person "Alice" 30
-- Same as: { name = "Alice", age = 30 }

Custom Types (Union Types)

This is where Elm gets really powerful. Custom types let you define your own types with specific variants:

type Color
    = Red
    | Green
    | Blue

type Status
    = Loading
    | Success String
    | Error String

Using Custom Types

type Status
    = Loading
    | Success String
    | Error String

showStatus : Status -> String
showStatus status =
    case status of
        Loading ->
            "Loading..."
        
        Success message ->
            "Success: " ++ message
        
        Error errorMsg ->
            "Error: " ++ errorMsg

showStatus Loading              -- "Loading..."
showStatus (Success "Done!")    -- "Success: Done!"
showStatus (Error "Not found")  -- "Error: Not found"

JavaScript Comparison

// JavaScript - using strings (error-prone)
const status = "loading";
if (status === "loading") { ... }
if (status === "laoding") { ... }  // Typo! No error

// JavaScript - using objects
const status = { type: "success", message: "Done" };
-- Elm - compiler catches typos
case status of
    Laoding -> ...  -- COMPILE ERROR: Laoding is not defined

Maybe: Handling Missing Values

Elm has no null or undefined. Instead, it uses the Maybe type:

type Maybe a
    = Just a
    | Nothing

-- Example: Safe dictionary lookup
Dict.get "name" myDict  -- Returns Maybe String

-- You MUST handle both cases
case Dict.get "name" myDict of
    Just value ->
        "Found: " ++ value
    
    Nothing ->
        "Not found"

This eliminates null pointer exceptions entirely!

Exercise 2.1: Define a Record Type

Create a type alias for a Book with:

  • title (String)
  • author (String)
  • pages (Int)
  • isRead (Bool)

Then create two book records.

Solution
type alias Book =
    { title : String
    , author : String
    , pages : Int
    , isRead : Bool
    }

book1 : Book
book1 =
    { title = "The Elm Guide"
    , author = "Evan Czaplicki"
    , pages = 150
    , isRead = True
    }

book2 : Book
book2 = Book "Learn You a Haskell" "Miran Lipovaca" 400 False

Exercise 2.2: Update a Record

Given this record:

player =
    { name = "Hero"
    , health = 100
    , score = 0
    }

Create a new record where:

  1. health is reduced to 80
  2. score is increased to 50
Solution
updatedPlayer =
    { player
        | health = 80
        , score = 50
    }

Exercise 2.3: Create a Custom Type

Create a TrafficLight type with Red, Yellow, and Green variants. Write a function canGo : TrafficLight -> Bool that returns True only for Green.

Solution
type TrafficLight
    = Red
    | Yellow
    | Green


canGo : TrafficLight -> Bool
canGo light =
    case light of
        Green ->
            True
        
        Yellow ->
            False
        
        Red ->
            False

-- Or more concisely:
canGo2 : TrafficLight -> Bool
canGo2 light =
    light == Green

Exercise 2.4: Maybe Practice

Write a function that takes a Maybe Int and returns the value doubled, or 0 if Nothing:

doubleOrZero : Maybe Int -> Int
Solution
doubleOrZero : Maybe Int -> Int
doubleOrZero maybeNum =
    case maybeNum of
        Just n ->
            n * 2
        
        Nothing ->
            0

Key Takeaways

  1. Elm has distinct Int and Float types - Unlike JavaScript's single "number"
  2. Type annotations are documentation - They make code self-explanatory
  3. Records are immutable - Use update syntax { record | field = value }
  4. Type aliases create reusable type names AND constructor functions
  5. Custom types replace string constants and are type-safe
  6. Maybe replaces null - Forces you to handle missing values explicitly

JavaScript to Elm Cheatsheet

JavaScript Elm
{} Record { field = value }
obj.field Same: record.field
{...obj, field: newVal} { record | field = newVal }
null / undefined Maybe type
String constants Custom types
=== ==
!== /=

What's Next?

In Lesson 3, we'll explore:

  • Pure functions
  • Higher-order functions
  • Currying and partial application
  • The pipe operator

← Previous: Lesson 1 | Next: Lesson 3 - Functions →