# 7. Sum types and pattern matching

> Chapter 7: enums, enums with data (sum types), case/in pattern matching, guards, and exhaustiveness checks that catch a forgotten case.

Canonical: https://alice-lang.org/documentation/learn/sum-types/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

Some values come in a few distinct shapes. A traffic light is red, amber or green. A shop command is "add a tea", "remove a tea", "show the total" or "quit", and each kind carries different data. This chapter shows how to describe such values exactly, and how the compiler then makes sure you handle every shape.

## 7.1 Enums: one of a fixed set

An `enum` lists the possible values of a type:

```alice
enum Light
  Red
  Amber
  Green
end

def next_light(light : Light) : Light
  case light
  when Light::Red then Light::Green
  when Light::Green then Light::Amber
  when Light::Amber then Light::Red
  end
end

puts next_light(Light::Red)
puts Light.values.size
```

```text title="Output"
Green
3
```

Why not use strings such as `"red"`? Because a typo like `"gren"` is still a valid string, and your program would quietly do the wrong thing. `Light::Gren` does not compile.

### The compiler counts the cases

Now suppose someone adds a fourth light, `Blinking`, and forgets to update `next_light`. In most languages, `next_light(Light::Blinking)` would fall through and return something unexpected at runtime. In Alice the program does not compile:

```alice fail="AL0201"
enum Light
  Red
  Amber
  Green
  Blinking
end

def next_light(light : Light) : Light
  case light
  when Light::Red then Light::Green
  when Light::Green then Light::Amber
  when Light::Amber then Light::Red
  end
end
```

```text title="Error"
error[AL0201]: non-exhaustive match on Light: missing Blinking
```

This check is called **exhaustiveness**. It turns "I forgot a case" from a bug report into a compile error that names the missing case. An `else` branch also satisfies it, but think before you add one: `else` hides the next new case too.

## 7.2 Sum types: shapes that carry data

A shop command is more than a name. "Add" needs a tea and a number of cups; "remove" needs only a tea; "total" needs nothing. Alice lets each enum variant carry its own fields:

```alice
enum Command
  Add(tea : String, cups : Int32)
  Remove(tea : String)
  Total
  Quit
end

add = Command::Add("Assam", 2)
puts add
puts add == Command::Add("Assam", 2)
```

```text title="Output"
Add { tea = Assam, cups = 2 }
true
```

A type that is "one of these shapes, each with its own data" is called a **sum type**. Rust calls it an enum, Haskell a data type with constructors, and Ruby has no direct equivalent. Variants compare by value, like records.

## 7.3 Taking values apart with `case ... in`

To use a sum type, you ask which shape it has and read its fields in one step. `case ... in` does that. Each `in` line is a **pattern**:

```alice
enum Command
  Add(tea : String, cups : Int32)
  Remove(tea : String)
  Total
  Quit
end

def describe(command : Command) : String
  case command
  in Command::Add(tea, cups) then "add #{cups} x #{tea}"
  in Command::Remove(tea) then "remove #{tea}"
  in Command::Total then "show the total"
  in Command::Quit then "quit"
  end
end

puts describe(Command::Add("Assam", 2))
puts describe(Command::Remove("Sencha"))
puts describe(Command::Quit)
```

```text title="Output"
add 2 x Assam
remove Sencha
quit
```

`in Command::Add(tea, cups)` matches only an `Add`, and binds its two fields to the names `tea` and `cups` for that branch. The check for a missing variant applies here too.

### Patterns beyond enums

Patterns work on ordinary values as well. A few forms are worth knowing:

```alice
def size_of(n : Int32) : String
  case n
  in 0 then "none"
  in 1 | 2 | 3 then "a few"
  in Int32 if n < 0 then "impossible"
  in ..9 then "several"
  in _ then "lots"
  end
end

[0, 2, -1, 7, 40].each { |n| puts size_of(n) }
```

```text title="Output"
none
a few
impossible
several
lots
```

| Pattern             | Matches                                      |
| ------------------- | -------------------------------------------- |
| `in 0`              | exactly that value                           |
| `in 1 \| 2 \| 3`    | any of the alternatives                      |
| `in Int32 if n < 0` | a type, with a **guard**: an extra condition |
| `in ..9`, `in 10..` | a range, open at one end                     |
| `in _`              | anything. Use it last, as a catch-all        |
| `in x`              | anything, and binds it to `x`                |
| `in ^limit`         | the current value of the variable `limit`    |

Arrays and hashes have patterns too. An array pattern checks the length and binds the elements; `*rest` takes the remainder:

```alice
def first_order(lines : Array(String)) : String
  case lines
  in [] then "no orders"
  in [only] then "just #{only}"
  in [first, *rest] then "#{first} and #{rest.size} more"
  end
end

puts first_order(Array(String).new(0, ""))
puts first_order(["Assam"])
puts first_order(["Assam", "Sencha", "Oolong"])
```

```text title="Output"
no orders
just Assam
Assam and 2 more
```

A hash pattern requires the listed keys and binds or compares their values:

```alice
request = {"lang" => "pt", "name" => "Ana"}
case request
in {"lang" => "pt", "name" => name} then puts "Olá, #{name}!"
in _ then puts "Hello!"
end
```

```text title="Output"
Olá, Ana!
```

## 7.4 Recursive sum types

A variant can contain values of its own type. That is how you describe trees, such as an arithmetic expression: a number, or a sum of two expressions, or a product of two expressions.

```alice
enum Expr
  Num(value : Int32)
  Add(left : Expr, right : Expr)
  Mul(left : Expr, right : Expr)
end

def eval(e : Expr) : Int32
  case e
  in Expr::Num(v) then v
  in Expr::Add(l, r) then eval(l) + eval(r)
  in Expr::Mul(l, r) then eval(l) * eval(r)
  end
end

# 2 + 3 * 4
e = Expr::Add(Expr::Num(2), Expr::Mul(Expr::Num(3), Expr::Num(4)))
puts eval(e)
```

```text title="Output"
14
```

The shape of `eval` follows the shape of the type: one branch per variant, and a recursive call where the type is recursive. Many programs, from compilers to game logic, are written this way.

## 7.5 Results instead of surprises

A function that can fail has two kinds of result. A sum type can say so in its return type, so the caller cannot forget the failure:

```alice
enum Parsed
  Ok(value : Int32)
  Failed(reason : String)
end

def parse_cups(text : String) : Parsed
  n = text.to_i?
  return Parsed::Failed("not a number: #{text}") unless n
  return Parsed::Failed("too many cups: #{n}") if n > 20
  Parsed::Ok(n)
end

["3", "lots", "50"].each do |text|
  case parse_cups(text)
  in Parsed::Ok(cups) then puts "#{cups} cups"
  in Parsed::Failed(reason) then puts reason
  end
end
```

```text title="Output"
3 cups
not a number: lots
too many cups: 50
```

Compare this with `T?`: `nil` says "nothing here" but not why. A result type carries the reason. [Chapter 8](https://alice-lang.org/documentation/learn/errors.md) shows the other tool, exceptions, and when to use which.

## Project: commands for the tea shop

The shop gets a command language. Text lines become `Command` values, and one `case` runs them. If someone adds a variant later, the compiler points at every `case` that must handle it.

```alice
enum Command
  Add(tea : String, cups : Int32)
  Remove(tea : String)
  Total
  Unknown(text : String)
end

MENU : Hash(String, Int32) = {"Assam" => 250, "Sencha" => 350}

def parse(line : String) : Command
  words = line.split(" ")
  case words
  in ["add", cups, tea] then Command::Add(tea, cups.to_i)
  in ["remove", tea] then Command::Remove(tea)
  in ["total"] then Command::Total
  in _ then Command::Unknown(line)
  end
end

basket = Hash(String, Int32).new(0)
["add 2 Assam", "add 1 Sencha", "remove Sencha", "dance", "total"].each do |line|
  case parse(line)
  in Command::Add(tea, cups) then basket[tea] += cups
  in Command::Remove(tea) then basket.delete(tea)
  in Command::Total then puts "total: #{basket.sum { |tea, n| MENU[tea] * n }}"
  in Command::Unknown(text) then puts "unknown command: #{text}"
  end
end
```

```text title="Output"
unknown command: dance
total: 500
```

:::caution\[Common mistakes]

- **Forgetting a variant.** `error[AL0201]` names the missing one. Add a branch for it.
- **Reaching for `else` too early.** An `else` silences the exhaustiveness check for future variants as well.
- **Mixing `when` and `in` habits.** `when` compares values (`when 1..5`). `in` matches patterns and binds names (`in Command::Add(tea, cups)`). Both check enums for exhaustiveness.
- **Putting `in _` first.** Patterns are tried in order, so a catch-all at the top hides every branch below it.
  :::

## Exercises

1. Define `enum Coin` with `Penny`, `Nickel`, `Dime` and `Quarter`, and a method `cents(c : Coin) : Int32`. Print the value of a dime.
2. Add `Neg(inner : Expr)` to the `Expr` type above. What does the compiler say about `eval`? Fix it, and print the value of `-(2 + 3)`.
3. Write `describe(xs : Array(Int32)) : String` that returns `"pair of N"` when the array has two equal elements, `"pair"` for any other two-element array, and `"other"` otherwise.

<details>
<summary>Answers</summary>

1. Answer:

   ```alice
   enum Coin
     Penny
     Nickel
     Dime
     Quarter
   end

   def cents(c : Coin) : Int32
     case c
     when Coin::Penny then 1
     when Coin::Nickel then 5
     when Coin::Dime then 10
     when Coin::Quarter then 25
     end
   end

   puts cents(Coin::Dime)
   ```

   ```text title="Output"
   10
   ```

2. Without a new branch, the compiler reports `error[AL0201]: non-exhaustive match on Expr: missing Neg`. With it:

   ```alice
   enum Expr
     Num(value : Int32)
     Add(left : Expr, right : Expr)
     Mul(left : Expr, right : Expr)
     Neg(inner : Expr)
   end

   def eval(e : Expr) : Int32
     case e
     in Expr::Num(v) then v
     in Expr::Add(l, r) then eval(l) + eval(r)
     in Expr::Mul(l, r) then eval(l) * eval(r)
     in Expr::Neg(x) then -eval(x)
     end
   end

   puts eval(Expr::Neg(Expr::Add(Expr::Num(2), Expr::Num(3))))
   ```

   ```text title="Output"
   -5
   ```

3. Bind both elements, then compare them in a guard:

   ```alice
   def describe(xs : Array(Int32)) : String
     case xs
     in [a, b] if a == b then "pair of #{a}"
     in [_, _] then "pair"
     in _ then "other"
     end
   end

   puts describe([4, 4])
   puts describe([4, 5])
   puts describe([1, 2, 3])
   ```

   ```text title="Output"
   pair of 4
   pair
   other
   ```

</details>

## Summary

- `enum` lists the values of a type. Variants with fields make a **sum type**: one of several shapes, each with its own data.
- `case ... in` matches patterns and binds names: variants, values, alternatives, ranges, guards, arrays and hashes.
- A `case` over an enum must cover every variant, or it is `AL0201`. The error names what is missing.
- Recursive sum types describe trees; the function that walks them mirrors the type.
- A result-like sum type makes failure part of the return type.

Next, [chapter 8](https://alice-lang.org/documentation/learn/errors.md) covers the errors that cannot be predicted: exceptions.

## Continue reading

- Previous: [6. Structs, records, classes and modules](https://alice-lang.org/documentation/learn/types.md)
- Next: [8. Errors](https://alice-lang.org/documentation/learn/errors.md)
