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

486 lines
8.8 KiB
Markdown

# 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
```elm
-- 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
// JavaScript - just "number"
const age = 30;
const price = 19.99;
```
### Strings
```elm
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:**
```javascript
const name = "Alice";
const poem = `
Roses are red...
`;
"Hello" + " World" // Uses + not ++
```
### Booleans
```elm
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
```elm
-- 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:
```elm
-- 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
```elm
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**.
```elm
-- 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
```elm
-- 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:
```elm
-- 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
// JavaScript - Mutation causes bugs
const user = { name: "Alice", score: 100 };
doSomething(user);
console.log(user.score); // Who knows? doSomething might have changed it!
```
```elm
-- 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:
```elm
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**:
```elm
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:
```elm
type Color
= Red
| Green
| Blue
type Status
= Loading
| Success String
| Error String
```
### Using Custom Types
```elm
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
// 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
-- 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:
```elm
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.
<details>
<summary>Solution</summary>
```elm
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
```
</details>
## Exercise 2.2: Update a Record
Given this record:
```elm
player =
{ name = "Hero"
, health = 100
, score = 0
}
```
Create a new record where:
1. health is reduced to 80
2. score is increased to 50
<details>
<summary>Solution</summary>
```elm
updatedPlayer =
{ player
| health = 80
, score = 50
}
```
</details>
## 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.
<details>
<summary>Solution</summary>
```elm
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
```
</details>
## Exercise 2.4: Maybe Practice
Write a function that takes a `Maybe Int` and returns the value doubled, or 0 if Nothing:
```elm
doubleOrZero : Maybe Int -> Int
```
<details>
<summary>Solution</summary>
```elm
doubleOrZero : Maybe Int -> Int
doubleOrZero maybeNum =
case maybeNum of
Just n ->
n * 2
Nothing ->
0
```
</details>
## 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](03-functions.md), we'll explore:
- Pure functions
- Higher-order functions
- Currying and partial application
- The pipe operator
---
[← Previous: Lesson 1](01-introduction.md) | [Next: Lesson 3 - Functions →](03-functions.md)