# Modules, Functions and Guards — Elixir & Phoenix

Source: https://www.geekswithgeeks.com/en/elixir-phoenix/c-functions

> Define named functions with multiple clauses, guards, defaults and anonymous functions.

## Functions are the unit of code

Code lives in **modules** (`defmodule Shop.Pricing do ... end`). Public functions use **`def`** and private ones **`defp`**. A function is identified by its name and **arity** (number of arguments), written `total/1`, so `total/1` and `total/2` are different functions. Functions can have **multiple clauses**: Elixir tries each clause top to bottom and runs the first whose **patterns** and **guards** match, which replaces many `if` statements. **Guards** (`when is_integer(qty) and qty > 0`) allow only a limited set of side-effect-free expressions; `defguard` defines reusable ones. Default arguments use `\\` (`def greet(name, greeting \\ "Hello")`). **Anonymous functions** are written `fn x -> x * 2 end` and called with a dot, `double.(4)`; the **capture operator** `&` gives shorthand: `&(&1 * 2)` or `&String.upcase/1`. Functions **return the last expression**; there is no `return` keyword. Use **`@doc`** and **`@moduledoc`** for documentation and **`@spec`** for type specifications. Module attributes such as `@gst_rate 0.18` act as compile-time constants.

## Multi-clause functions with guards

Pattern matching in function heads replaces conditionals.

```elixir
defmodule Shop.Pricing do
  @moduledoc "Price calculations in paise."
  @gst_rate_percent 18

  @doc "Shipping fee for an order total in paise."
  @spec shipping_fee(non_neg_integer(), keyword()) :: non_neg_integer()
  def shipping_fee(total, opts \\ [])
  def shipping_fee(total, _opts) when total >= 100_000, do: 0
  def shipping_fee(_total, opts) do
    if Keyword.get(opts, :express, false), do: 9_900, else: 4_900
  end

  def with_gst(paise) when is_integer(paise) and paise >= 0 do
    paise + div(paise * @gst_rate_percent + 50, 100)
  end

  def describe({:ok, total}), do: "Total: #{format(total)}"
  def describe({:error, :empty_cart}), do: "Your cart is empty"
  def describe({:error, reason}), do: "Could not price order: #{inspect(reason)}"

  defp format(paise), do: :erlang.float_to_binary(paise / 100, decimals: 2)
end

IO.puts(Shop.Pricing.shipping_fee(150_000))              # 0
IO.puts(Shop.Pricing.shipping_fee(20_000, express: true)) # 9900
IO.puts(Shop.Pricing.describe({:ok, Shop.Pricing.with_gst(10_000)}))   # Total: 118.00

double = fn x -> x * 2 end
IO.inspect(Enum.map([1, 2, 3], double))
IO.inspect(Enum.map(["pune", "delhi"], &String.capitalize/1))
```

## Clause order matters

Clauses are tried top to bottom, so put specific clauses first. The compiler warns when a later clause can never match because an earlier one is more general.

**Quiz:** In Elixir, how are total/1 and total/2 related?

- [ ] They are the same function
- [x] They are different functions because arity is part of a function's identity
- [ ] total/2 overrides total/1
- [ ] Defining both is an error

*Answer:* They are different functions because arity is part of a function's identity. Functions are identified by name and arity.
