# 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.
Solution ```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 ```
## 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
Solution ```elm 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 ```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 ```
## 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 ```
Solution ```elm 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](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)