# Attempt to Learn Elixir: Functions and Patterns

Elixir is a functional programming language. Functional languages are built with functions as first-class citizens and tend to involve a declarative style of programming, where we specify what is to be done rather than focusing on how to do it. Functions are meant to be small and simple, and bigger functions are made by composing the smaller functions together.

## Pattern Matching & Immutability

In Elixir `=` is not assignment operator. It's the **match operator**. It asserts that the left side *matches* the right side, and binds any unbound variables on the left to make that true.

```elixir
x = 1
```

Read this as: "Match the pattern `x` against the value `1`. `x` is unbound, so bind it to `1` to make the match succeed." It happens to look like assignment, which is the trap.

Now the part that makes us confused:

```elixir
1 = x   # This is legal. It succeeds.
```

Here `x` is already bound to `1`. The left pattern `1` matches the right value `1`. Match succeeds, nothing is bound. In many languages this is a syntax error; in Elixir it's a true assertion.

```elixir
2 = x   # ** (MatchError) no match of right hand side value: 1
```

`2` does not match `1`. **MatchError.** This is not an exception you fear, it's the language telling you your assumption about a value's shape was wrong. You will use this deliberately.

In Elixir, data is immutable. You don't mutate; you produce a *new* value:

```elixir
xs = [1, 2, 3]
List.insert_at(xs, 3, 4)   # returns [1, 2, 3, 4]
xs                         # still [1, 2, 3] — unchanged
```

"But isn't copying everything slow?" No, the runtime uses *persistent data structures* with structural sharing. The new list shares most of its memory with the old one; only the changed spine is new. You get value semantics without the copy cost you'd fear in C.

**Why you should care?**

With no shared mutable state, two processes can run truly in parallel with zero locks. The entire "let it crash and respawn" model depends on this: a crashing process can't have left shared state half-written, because there *is* no shared mutable state. This is the foundation of the fault tolerance you're here for.

### Destructuring: matching against shapes

Matching gets powerful when the left side is a *structure*. The match binds the pieces.

```elixir
[a, b, c] = [1, 2, 3]   # a = 1, b = 2, c = 3
{status, payload} = {:ok, "hello"}   # status = :ok, payload = "hello"
%{name: n} = %{name: "Shah", role: "lead"}   # n = "Shah"; extra keys ignored
```

Lists have a special, deeply important pattern — **head and tail**:

```elixir
[head | tail] = [1, 2, 3]   # head = 1, tail = [2, 3]
[first, second | rest] = [10, 20, 30, 40]   # first=10, second=20, rest=[30,40]
```

This `[head | tail]` pattern *is* how recursion over lists works in Elixir.

A match that doesn't fit raises and that's a feature:

```elixir
{:ok, value} = {:error, :timeout}   # ** (MatchError)
```

This is the heart of the **tagged-tuple convention** that pervades Elixir and OTP. Functions return `{:ok, result}` or `{:error, reason}`, and you match to branch.

* * *

## **Functions, Multiple Clauses & Guards**

**Elixir functions are pattern matching with a name.** A single "function" is really a stack of clauses, and the runtime picks one by matching the arguments. Combined with guards, this replaces most of the `if/else if/switch` ladders you write in Go and Python with flat, declarative dispatch.

Our instinct will be to write one function with a `case`/`if` chain inside. But the Elixir way is many small clauses. Learning to think in clauses is most of becoming fluent.

**No** `return`**.** A function returns its *last expression*. Always. There is no early return; you restructure with clauses instead. This feels restrictive for about a day, then liberating.

`do:` **shorthand vs** `do…end`**.** One-liners use `do:`; multi-line bodies use a block:

```elixir
def classify(code), do: code            # shorthand
def classify(code) do                   # block form
  String.upcase(code)
end
```

**Public vs private:** `def` is callable from outside the module; `defp` is private to it.

**Functions are identified by name *and arity* —** `name/arity`**.** This is genuinely new coming from other languages. `classify/1` and `classify/2` are *different functions*, not overloads of one. When an error says `classify/1`, the `/1` is the argument count. Internalize this notation; the whole ecosystem speaks it.

```elixir
defmodule Demo do
  def f(a), do: {:one, a}
  def f(a, b), do: {:two, a, b}   
  # f/2 is a separate function from f/1
end
```

### Multiple clauses (dispatch by shape)

This is the heart of the lesson. You write several clauses with the *same name and arity*; the runtime tries them **top to bottom** and runs the first whose pattern matches.

```elixir
defmodule Cwmp.Event do
  def classify("0 BOOTSTRAP"),          do: :bootstrap
  def classify("1 BOOT"),               do: :boot
  def classify("2 PERIODIC"),           do: :periodic
  def classify("4 VALUE CHANGE"),       do: :value_change
  def classify("6 CONNECTION REQUEST"), do: :connection_request
  def classify(_other),                 do: :unknown
end

Cwmp.Event.classify("1 BOOT")     # => :boot
Cwmp.Event.classify("9 WHATEVER") # => :unknown
```

Two rules that will bite you if ignored:

**Order matters.** Clauses are tried in source order. The catch-all `_other` clause **must be last**. Put it first and it shadows everything below, which Elixir will even warn you about ("this clause cannot match").

**Matching extends to structure**, not just literals. You can destructure arguments in the head.

```elixir
def summarize({:inform, id, %{event: e}}), do: "#{id}: #{classify(e)}"
def summarize(_),                          do: "not an inform"
```

This is the replacement for the imperative `if type == ... else if ...` ladder. Each branch is its own clause, self-documenting, and impossible to fall through by accident.

### Guards (`when`)

Patterns match *shape*. Guards add *conditions -* "this clause matches **when** some test also holds":

```elixir
defmodule Health do
  def level(p) when p < 0.5,  do: :ok
  def level(p) when p < 0.8,  do: :warn
  def level(_p),              do: :critical
end

Health.level(0.3)  # :ok
Health.level(0.9)  # :critical
```

Three things you must know about guards, because they are *not* normal code:

**Only a restricted set of operations is allowed in a guard.** Comparisons (`<`, `==`, …), boolean `and`/`or`/`not`, arithmetic, type checks (`is_binary/1`, `is_integer/1`, `is_map/1`, …), `in`, and a handful of Kernel functions (`hd`, `tl`, `map_size`, `byte_size`, `length`, `elem`). Arbitrary function calls are **forbidden**. `when String.contains?(code, "BOOT")` will *not compile*. The reason: guards must be pure, fast, and side-effect-free so the runtime can evaluate them safely during dispatch. This trips up every newcomer from imperative languages. Your reflex to call a helper in the condition won't work.

**A guard that errors does not crash — the clause just fails.** If a guard raises (say, arithmetic on a non-number), Elixir doesn't propagate the error; it silently treats that clause as "no match" and tries the next. This is deliberate and occasionally surprising when debugging.

**Combine guards with destructuring** for real power:

```elixir
def valid?(%{event: e}) when is_binary(e) and byte_size(e) > 0, do: true
def valid?(_), do: false
```

Pattern matching and immutable data are the foundational axioms from which the entire programming language is derived. If you truly internalize these two, the rest of Elixir (pipelines, OTP, processes, and even macros) stops feeling like a list of features to memorize and starts feeling like inevitable, elegant consequences. Learning these concepts and shifting your mental model towards them is the key to mastering Elixir and functional programming.
