# AP1 · 2 — Des fonctions qui reconnaissent leurs arguments

## Notre question

Comment écrire une fonction qui choisit elle-même son cas, sans cascade
de `if` ? Nous écrivons un module, puis des clauses dont les paramètres
sont des motifs.

> **Repères.** Poly, chap. 2 « Le pattern matching » et chap. 4, sections 4.1 à 4.3. Le cours se fait ici, dans le carnet.

> 💡 Durée indicative : 25 min, reprise collective comprise. Ce carnet
> est autonome : aucune cellule d'un autre carnet n'est nécessaire. Je le
> projette au tableau ; écrivez une prédiction avant d'exécuter, puis
> expliquez un résultat qui vous a surpris. **Sauvegardez-le dès maintenant
> sous `AP1/ap1-02-fonctions-motifs.livemd`.**

Les cellules `assert` sont des vérifications fournies : si une condition
est fausse, Livebook montre le résultat obtenu et celui attendu.
Évaluez cet import une fois, puis réévaluez votre code avant ses tests
après chaque modification.

```elixir
import ExUnit.Assertions
```

## E2 — Écrire une fonction complète

Un module regroupe des fonctions. `def` définit une fonction publique ;
ses paramètres deviennent les noms utilisables dans son corps.
La dernière expression fournit le résultat. Les noms de modules commencent
par une majuscule, ceux des fonctions par une minuscule.

```elixir
defmodule Greeting do
  def hello(name) do
    "Bonjour " <> name
  end
end

Greeting.hello("Ada")
```

`<>` assemble deux chaînes. Écrivez maintenant le module `Tickets` et sa
fonction `total/2`, qui reçoit un prix et une quantité et renvoie leur
produit. Aucun affichage n'est nécessaire. Vous choisissez les noms
des paramètres ; les deux appels ci-dessous sont son contrat.

```elixir
defmodule Tickets do
  # Écrivez total/2.
end
```

```elixir
assert Tickets.total(7, 3) == 21
assert Tickets.total(5, 0) == 0
```

Expliquez la différence entre le paramètre de votre définition et
l'argument `7` de cet appel. En Elixir, l'arité fait partie du nom de la
fonction : `total/1` et `total/2` seraient deux fonctions différentes.

## Un motif peut défaire ce que nous avons construit

Dans `{:ok, amount} = ticket`, la partie gauche est un *motif* :
elle décrit la forme attendue. L'atome exige la valeur `:ok`, tandis
que le nom `amount` reçoit le deuxième élément.

```elixir
ticket = {:ok, 21}
{:ok, amount} = ticket
amount
```

Prédisez ce qui se passerait avec `{:error, :unknown_category}` à droite.
Essayez dans une cellule séparée, puis commentez cette tentative pour
pouvoir relancer le carnet. Une correspondance impossible lève
`MatchError` : `=` ne choisit pas une branche de secours.

## E3 — Choisir une clause

Les paramètres d'une fonction sont également des motifs. Elixir essaie
les clauses de haut en bas et exécute la première qui correspond.
`do: expression` est la forme courte de `do … end` pour une expression.
Une variable dans un motif accepte toute valeur. Si nous ne la réutilisons
pas, nous écrivons `_` ou un nom qui commence par `_`, par ex. `_category`.

Complétez `Tariff` : un plein tarif vaut 7, un tarif réduit 5 et une
catégorie inconnue renvoie `{:error, :unknown_category}`. `total/2`
reste réservé aux deux catégories connues. Où placer le cas général ?

```elixir
defmodule Tariff do
  def price(:full), do: 7
  # Ajoutez le tarif réduit, puis le cas général.

  def total(category, quantity) do
    price(category) * quantity
  end
end
```

```elixir
assert Tariff.total(:full, 3) == 21
assert Tariff.total(:reduced, 3) == 15
assert Tariff.price(:visitor) == {:error, :unknown_category}
```

Un cas général placé en premier masque donc
les suivants. Pourquoi `Tariff.total(:visitor, 3)` ne respecte-t-il pas
notre contrat ? Nous apprendrons à propager une erreur sans faire de
calcul avec elle.

Une *garde* ajoute une condition à un motif. Voici une fonction à lire :
le motif nomme la valeur, puis `when` exige un entier positif ou nul.
`is_integer(value)` teste si la valeur est un entier ; il ne convertit
pas la chaîne `"3"` en nombre.

```elixir
defmodule Quantity do
  def check(value) when is_integer(value) and value >= 0, do: {:ok, value}
  def check(_value), do: {:error, :expected_quantity}
end

{Quantity.check(3), Quantity.check(-1), Quantity.check("3")}
```

Nous n'avons pas besoin d'apprendre toutes les gardes. Retenez leur
place et la lecture « ce motif, à condition que… ». `and` relie ici
deux tests booléens ; `==`, `!=`, `<` et `>=` comparent les valeurs.

## Garder une trace

Notez, dans une cellule de texte, pourquoi le cas général doit venir en
dernier et ce qui se passe quand il vient en premier. Un compilateur le
signale ; sachez lire cet avertissement.

Enregistrez ce carnet sous `AP1/ap1-02-fonctions-motifs.livemd` dans votre dépôt privé
`ap-homework`, puis ouvrez `ap1-03-listes-recursion.livemd` depuis la page AP1 quand nous
y passons ensemble. Chaque carnet repart d'une session neuve : vos modules
précédents ne sont pas chargés, et ce n'est pas nécessaire.

Un bout de syntaxe oublié ? Les chapitres 1 à 4 du [poly](https://ap-2026.noce.fr/poly/poly-elixir.pdf)
reprennent les formes du langage, chacune avec un exemple et son résultat ;
le cours, lui, se fait dans les carnets.

Pour toute question, vous pouvez me contacter par email :
[aurelien.noce@minesparis.psl.eu](mailto:aurelien.noce@minesparis.psl.eu).
