# Lesson 3: Functions ## Learning Goals By the end of this lesson, you will: - Write pure functions - Understand currying and partial application - Use higher-order functions (map, filter, reduce) - Use the pipe operator for readable code - Understand function composition ## Defining Functions ### Basic Syntax ```elm -- JavaScript // const greet = (name) => "Hello, " + name; -- Elm greet : String -> String greet name = "Hello, " ++ name ``` Key differences: - No parentheses around parameters - No `return` keyword - No curly braces - Last expression is automatically returned ### Multiple Parameters ```elm -- JavaScript // const add = (a, b) => a + b; -- Elm add : Int -> Int -> Int add a b = a + b -- Using it add 5 3 -- 8 (no parentheses or commas!) ``` ### If-Then-Else ```elm -- JavaScript // const abs = x => x < 0 ? -x : x; -- Elm abs : Int -> Int abs x = if x < 0 then -x else x ``` **Important:** In Elm, `if` is an **expression** that returns a value, not a statement. Both branches must return the same type. ```elm -- This won't compile: invalid x = if x > 0 then "positive" -- Missing else! What would it return? ``` ## Pure Functions In Elm, ALL functions are **pure**: 1. **Same input → Same output** (always) 2. **No side effects** (no I/O, no mutation, no randomness) ```javascript // JavaScript - Impure function let counter = 0; function increment() { counter += 1; // Side effect: modifies external state return counter; } increment(); // 1 increment(); // 2 - Different result! ``` ```elm -- Elm - Pure function increment : Int -> Int increment counter = counter + 1 increment 0 -- 1 increment 0 -- 1 (always!) ``` ### Benefits of Pure Functions 1. **Testable** - No mocks needed, just input/output 2. **Cacheable** - Same input = same output, so cache results 3. **Predictable** - No hidden state changes 4. **Parallelizable** - Safe to run simultaneously ## Currying and Partial Application ### What is Currying? In Elm, every function takes exactly one argument. A function with "multiple arguments" is actually a chain of functions. ```elm add : Int -> Int -> Int add a b = a + b -- This is actually: add : Int -> (Int -> Int) -- A function that takes an Int and returns a function that takes an Int ``` ### Partial Application You can apply some arguments now and the rest later: ```elm add : Int -> Int -> Int add a b = a + b addFive : Int -> Int addFive = add 5 -- Partially applied! addFive 3 -- 8 addFive 10 -- 15 ``` **JavaScript equivalent:** ```javascript const add = a => b => a + b; const addFive = add(5); addFive(3); // 8 ``` ### Practical Example ```elm -- A formatting function format : String -> String -> String format prefix text = prefix ++ ": " ++ text -- Create specialized formatters formatError : String -> String formatError = format "ERROR" formatWarning : String -> String formatWarning = format "WARNING" formatError "File not found" -- "ERROR: File not found" formatWarning "Low memory" -- "WARNING: Low memory" ``` ## Anonymous Functions (Lambdas) ```elm -- JavaScript // const double = x => x * 2; // numbers.map(x => x * 2); -- Elm double = \x -> x * 2 List.map (\x -> x * 2) numbers ``` The `\` represents the Greek letter lambda (λ). ### Multiple Parameters ```elm -- JavaScript // const add = (a, b) => a + b; -- Elm add = \a b -> a + b -- Or equivalently add = \a -> \b -> a + b ``` ## Higher-Order Functions Higher-order functions take or return other functions. ### List.map (like Array.map) ```elm -- JavaScript // [1, 2, 3].map(x => x * 2) -- Elm List.map (\x -> x * 2) [1, 2, 3] -- [2, 4, 6] -- Or with a named function double x = x * 2 List.map double [1, 2, 3] -- [2, 4, 6] ``` ### List.filter (like Array.filter) ```elm -- JavaScript // [1, 2, 3, 4, 5].filter(x => x > 3) -- Elm List.filter (\x -> x > 3) [1, 2, 3, 4, 5] -- [4, 5] ``` ### List.foldl / List.foldr (like Array.reduce) ```elm -- JavaScript // [1, 2, 3].reduce((sum, x) => sum + x, 0) -- Elm List.foldl (\x sum -> sum + x) 0 [1, 2, 3] -- 6 -- Or using the (+) operator as a function List.foldl (+) 0 [1, 2, 3] -- 6 ``` Note: Arguments are in different order than JavaScript! - `foldl` goes left-to-right - `foldr` goes right-to-left ### Other Useful Functions ```elm List.head [1, 2, 3] -- Just 1 (returns Maybe!) List.tail [1, 2, 3] -- Just [2, 3] List.take 2 [1, 2, 3, 4] -- [1, 2] List.drop 2 [1, 2, 3, 4] -- [3, 4] List.reverse [1, 2, 3] -- [3, 2, 1] List.sort [3, 1, 2] -- [1, 2, 3] List.member 2 [1, 2, 3] -- True List.length [1, 2, 3] -- 3 ``` ## The Pipe Operator |> The pipe operator makes chains of functions readable: ```elm -- Without pipes (hard to read, inside-out) String.toUpper (String.trim (String.reverse " hello ")) -- With pipes (clear data flow) " hello " |> String.reverse |> String.trim |> String.toUpper -- Result: "OLLEH" ``` **How it works:** `x |> f` is the same as `f x` ### JavaScript Comparison ```javascript // JavaScript method chaining (only works with methods) " hello ".split("").reverse().join("").trim().toUpperCase() // JavaScript pipe (proposed, not standard) " hello " |> reverse |> trim |> toUpperCase ``` ### Complex Example ```elm -- Get the names of users over 21, sorted users |> List.filter (\user -> user.age > 21) |> List.map .name |> List.sort ``` ## Function Composition Combine functions into new functions: ```elm -- The >> operator (left to right) addOne = (+) 1 double = (*) 2 addOneThenDouble : Int -> Int addOneThenDouble = addOne >> double addOneThenDouble 5 -- 12 (5+1=6, 6*2=12) -- The << operator (right to left) doubleThenAddOne : Int -> Int doubleThenAddOne = addOne << double doubleThenAddOne 5 -- 11 (5*2=10, 10+1=11) ``` ### Pipe vs Composition ```elm -- Pipe: Apply to a specific value 5 |> addOne |> double -- 12 -- Composition: Create a new function addOneThenDouble = addOne >> double addOneThenDouble 5 -- 12 ``` ## Let Expressions Use `let...in` for local bindings: ```elm -- JavaScript // function circleArea(radius) { // const pi = 3.14159; // const squared = radius * radius; // return pi * squared; // } -- Elm circleArea : Float -> Float circleArea radius = let pi = 3.14159 squared = radius * radius in pi * squared ``` You can also define helper functions: ```elm pythagoras : Float -> Float -> Float pythagoras a b = let square x = x * x in sqrt (square a + square b) ``` ## Exercise 3.1: Basic Functions Write these functions: 1. `isEven : Int -> Bool` - Returns True if number is even 2. `exclaim : String -> String` - Adds "!" to the end of a string 3. `greet : String -> String -> String` - Takes a greeting and a name
Solution ```elm isEven : Int -> Bool isEven n = modBy 2 n == 0 exclaim : String -> String exclaim text = text ++ "!" greet : String -> String -> String greet greeting name = greeting ++ ", " ++ name ++ "!" ```
## Exercise 3.2: Higher-Order Functions Given this list: ```elm numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ``` Use List functions to: 1. Get only the odd numbers 2. Square each number 3. Get the sum of all numbers
Solution ```elm -- 1. Odd numbers List.filter (\n -> modBy 2 n /= 0) numbers -- [1, 3, 5, 7, 9] -- 2. Squared List.map (\n -> n * n) numbers -- [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] -- 3. Sum List.foldl (+) 0 numbers -- Or: List.sum numbers -- 55 ```
## Exercise 3.3: Pipe Operator Rewrite this using the pipe operator: ```elm String.fromInt (List.length (List.filter isEven (List.range 1 100))) ```
Solution ```elm List.range 1 100 |> List.filter isEven |> List.length |> String.fromInt -- "50" ```
## Exercise 3.4: Partial Application Create a `greetUser` function by partially applying this: ```elm greet : String -> String -> String greet greeting name = greeting ++ ", " ++ name ++ "!" ``` So that `greetUser "Alice"` returns `"Hello, Alice!"`
Solution ```elm greetUser : String -> String greetUser = greet "Hello" greetUser "Alice" -- "Hello, Alice!" greetUser "Bob" -- "Hello, Bob!" ```
## Key Takeaways 1. **All Elm functions are pure** - Same input always gives same output 2. **All functions are curried** - Multi-arg functions are chains of single-arg functions 3. **Partial application** lets you create specialized functions 4. **The pipe operator `|>`** makes data transformation chains readable 5. **Higher-order functions** like `map`, `filter`, `foldl` are your primary tools 6. **`let...in`** provides local bindings within functions ## Common Patterns ```elm -- Transform a list items |> List.map transform -- Filter and transform items |> List.filter condition |> List.map transform -- Find something items |> List.filter condition |> List.head -- Aggregate items |> List.foldl combiner initial ``` ## What's Next? In [Lesson 4](04-tea.md), we'll learn The Elm Architecture (TEA): - Model: Your application state - View: Turning state into HTML - Update: Handling messages and updating state This is the heart of Elm applications and inspired Redux! --- [← Previous: Lesson 2](02-basics.md) | [Next: Lesson 4 - The Elm Architecture →](04-tea.md)