# AP6 — Des processus à une application

## Où un programme peut-il garder une valeur ?

Jusqu'ici, nos fonctions recevaient leurs données en arguments et rendaient
une nouvelle valeur. Nous voudrions maintenant qu'un compteur reste disponible
entre deux appels, et que plusieurs utilisateurs puissent lui écrire.
Où mettre ce nombre si nos valeurs sont immuables ?

Nous allons créer des *processus*, leur envoyer des messages, puis faire
vivre notre compteur dans l'un d'eux. Un processus est ici une unité
d'exécution de la BEAM, la machine virtuelle d'Erlang sur laquelle tourne
aussi Elixir. Il possède sa propre mémoire, un identifiant, le *PID*, qui
sert d'adresse pour lui écrire, et une *boîte aux lettres*, c-à-d. la file
des messages qu'il a reçus et pas encore lus. Deux processus ne partagent
aucune donnée : ils ne peuvent que s'envoyer des messages.

> **Repères.** [Slides AP6, pages 1 à 34](https://ap-2026.noce.fr/slides/AP6.html#1) :
> un court exposé ouvre chaque partie ; le carnet est notre support de cours.
> [Poly, chap. 9 « Processus, messages et état »](https://ap-2026.noce.fr/poly/poly-elixir.pdf),
> sections « Qui exécute cette fonction ? », « Envoyer n'est pas attendre
> une réponse », « Comment l'état reste-t-il vivant ? », « Observer une
> panne », « Laisser planter, puis relancer » et « Sortir du carnet avec
> Mix » ; Agent et GenServer viendront en AP7, au chapitre 10.
> [Guide Elixir des processus](https://hexdocs.pm/elixir/processes.html).
> Formes utilisées : fonctions anonymes, tuples, motifs épinglés `^ref`,
> `receive`, `try` et récursion terminale.

Ce carnet est autonome, sans bibliothèque à installer ni carnet précédent
à recopier. Gardez vos prédictions et vos observations dans les cellules
de texte. Nous préciserons en cours les exercices à terminer et le point
d'arrêt attendu ; aucune nouvelle échéance n'est fixée ici.

Pour la fin de séance, Elixir, Erlang et l'éditeur doivent avoir été préparés
sur votre poste suivant la page [Pré cours 6](https://ap-2026.noce.fr/avant-ap6/). Livebook Desktop dispose
de son propre environnement ; cela ne garantit pas que `mix` soit disponible
dans votre terminal.

### Quelques précautions dans Livebook

Une cellule est elle-même évaluée par un processus. Ne supposez pas que
deux cellules ont le même `self()` ou partagent leur boîte aux lettres :
**un échange complet reste dans une seule cellule**. En particulier, prenez
l'adresse du client, lancez le processus, envoyez la demande et recevez
sa réponse sans changer de cellule.

Réévaluer un module ne termine pas les processus déjà lancés. Les cellules
de vérification arrêtent donc leurs processus dans un `try ... after`, un
bloc dont la partie `after` s'exécute quoi qu'il arrive, même si une
assertion échoue. Gardez ce nettoyage quand vous les modifiez.
Si une cellule modifiée reste bloquée, arrêtez son évaluation ; redémarrer
le runtime permet de repartir sans processus oubliés.

Les `assert` ci-dessous sont fournis : une condition fausse signale le souci.
Les cellules à compléter lèvent volontairement une erreur ; remplacez le
`raise` par votre code, puis réévaluez la définition avant ses vérifications.

```elixir
import ExUnit.Assertions
```

## 1 · Qui exécute cette fonction ?

`self()` rend le *PID* (identifiant) du processus courant. `spawn(fn -> ... end)`
crée un autre processus, y lance la fonction et rend son PID au créateur.
Lorsque la fonction se termine, le nouveau processus se termine aussi.

Un processus BEAM n'est pas un processus du système d'exploitation : la VM
organise l'exécution de nombreux processus légers sur ses ordonnanceurs.
Ils peuvent progresser concurremment ; l'ordre dans lequel nous observons
leurs actions n'est pas un outil de synchronisation.

Pour savoir qui exécute quoi, l'enfant va nous écrire. `send(pid, message)`
dépose un message dans la boîte aux lettres du processus `pid` ;
`receive` attend, dans notre propre boîte, un message qui correspond à
l'un de ses motifs, et `after` borne cette attente. Nous détaillons les
autres lignes juste après. Dans cet exemple, prévoyez si les deux PID
seront égaux. Pourquoi prenons-nous `parent = self()` avant d'appeler
`spawn` ?

```elixir
parent = self()
ref = make_ref()
child = spawn(fn -> send(parent, {:hello, ref, self()}) end)

received =
  receive do
    {:hello, ^ref, pid} -> pid
  after
    1_000 -> raise "Le processus n'a pas répondu"
  end

assert received == child
assert received != parent
{parent, received}
```

Le `self()` dans la fonction anonyme désigne l'enfant. La variable `parent`,
capturée par cette fonction, lui donne l'adresse de retour. `send(pid, valeur)`
envoie une valeur et rend cette même valeur ; il n'attend pas que le destinataire
la lise. Ici le message est un tuple, dont l'atome `:hello` indique la nature.

Le `receive` attend un message qui correspond au motif. `make_ref()` produit
une référence unique pour cet essai ; `^ref` exige la référence déjà créée,
au lieu de donner une nouvelle valeur au nom `ref`. Nous reviendrons sur
l'intérêt de cette précaution lorsque plusieurs réponses se croiseront.
Le `after 1_000` borne l'attente à une seconde environ : si aucun message
ne correspond dans ce délai, c'est sa branche qui s'exécute, ici un `raise`.
Il n'endort pas volontairement le programme et ne garantit pas une réponse
en une seconde.

À vous : ajoutez votre prénom au message, puis adaptez le motif du `receive`.
Gardez la référence et le PID pour vérifier que vous recevez le bon message.

## 2 · Comment demander une réponse ?

Nous allons écrire un premier *service*, c-à-d. un processus qui attend
des demandes et y répond. Il ne répondra qu'à une demande, puis
s'arrêtera. La demande doit contenir l'adresse du client, sinon le
service ne saura pas à qui répondre.

| Message reçu par le service | Réponse envoyée au client |
| --- | --- |
| `{:double, client, ref, number}` | `{:result, ref, 2 * number}` |

Complétez `DoubleServer.once/0`. La fonction attend le message ci-dessus,
calcule le double et envoie sa réponse à `client`. Conservez un `after`
qui termine la fonction avec `:idle` si rien ne correspond après 1 000 ms.
Nous n'utilisons ce délai d'inactivité que pour ce premier service à usage unique.

💡 Un `receive` ressemble au `case` que nous connaissons : des motifs, puis
une expression à droite de `->`. Sa donnée à examiner vient toutefois de
la boîte aux lettres du processus.

```elixir
defmodule DoubleServer do
  def once(), do: raise("Écrivez le receive et la réponse")
end
```

La création et l'échange se font dans cette même cellule. Le `after` du
`try` est un nettoyage, comme un `finally` en Python : il est exécuté aussi
en cas d'erreur, et `Process.exit(server, :kill)` y arrête sans ménagement
le processus que nous venons de créer. Cet `after` ne joue pas le même rôle
que le délai du `receive`. La vérification `assert_receive` est nouvelle
elle aussi ; nous l'expliquons juste après.

```elixir
server = spawn(fn -> DoubleServer.once() end)
ref = make_ref()

try do
  sent = send(server, {:double, self(), ref, 21})
  assert sent == {:double, self(), ref, 21}
  assert_receive {:result, ^ref, 42}, 1_000
after
  Process.exit(server, :kill)
end
```

`assert_receive` est la vérification fournie par ExUnit : elle attend le
motif indiqué, ici pendant au plus 1 000 ms. Elle échoue si aucune réponse
correspondante n'arrive. Le premier `assert`, lui, vérifie seulement ce
qu'a rendu `send` ; il ne prouve pas que le service a travaillé.

Ajoutez un essai avec un nombre négatif, en lançant un nouveau processus.
Pourquoi faut-il un nouveau processus ? Décrivez ce qui se passerait si
`once/0` remplaçait `client` par `self()` au moment de répondre.

## 3 · Une boîte non vide peut-elle attendre ?

Les messages qui ne correspondent à aucun motif restent dans la boîte.
Le `receive` parcourt les messages dans l'ordre d'arrivée et prend le
premier qui correspond à l'une de ses clauses. On appelle cela une
*réception sélective*.

Prévoyez le résultat avant d'exécuter : le premier message envoyé sera-t-il
le premier retiré ? Nous écrivons dans notre propre boîte pour choisir
nous-mêmes l'ordre d'arrivée, sans dépendre de la vitesse de deux processus.

```elixir
tag = make_ref()
send(self(), {:notice, tag, "arrivé en premier"})
send(self(), {:answer, tag, 42})

answer =
  receive do
    {:answer, ^tag, value} -> value
  after
    1_000 -> raise "Réponse absente"
  end

notice =
  receive do
    {:notice, ^tag, text} -> text
  after
    1_000 -> raise "Annonce absente"
  end

assert {answer, notice} == {42, "arrivé en premier"}
{answer, notice}
```

Le message ignoré n'a donc pas disparu. Avec une clause attrape-tout
`message -> ...`, en revanche, nous l'aurions retiré de la boîte.
Un `receive` attend quand aucun message ne correspond, même si la boîte
contient déjà autre chose.

Nous voulons maintenant attendre la réponse à la première demande, alors
que la réponse à la seconde est déjà arrivée. Écrivez `Replies.await/2` :
elle reçoit `{:result, ref, value}` pour la référence demandée et rend
`{:ok, value}`. Après `timeout` millisecondes, elle rend `{:error, :timeout}`.
Ne consommez pas les réponses destinées aux autres demandes.

```elixir
defmodule Replies do
  def await(_ref, _timeout), do: raise("Attendez la bonne référence")
end
```

```elixir
first = make_ref()
second = make_ref()
send(self(), {:result, second, 200})
send(self(), {:result, first, 100})

assert Replies.await(first, 1_000) == {:ok, 100}
assert Replies.await(second, 1_000) == {:ok, 200}
assert Replies.await(make_ref(), 0) == {:error, :timeout}
```

Pourquoi le test avec `second` trouve-t-il encore sa réponse ? Retirez
provisoirement `^` devant `ref`, prévoyez le souci, puis rétablissez-le.

Les messages envoyés d'un même processus vers un même destinataire
arrivent dans leur ordre d'envoi. Cela n'impose pas d'ordre global entre
deux expéditeurs, et la réception sélective peut retirer un message situé
plus loin dans la boîte.

Enfin, un délai dépassé signifie seulement que nous cessons d'attendre.
Il n'annule ni le message déjà envoyé ni le travail du service. Une réponse
tardive peut encore arriver : notre petit protocole ne prévoit pas de
relance automatique, notamment pour une commande qui modifie l'état.
Si un ajout a été traité mais sa réponse arrive trop tard, renvoyer l'ajout
peut le compter deux fois. La référence corrèle une réponse ; notre service
ne la mémorise pas pour détecter une commande répétée.

## 4 · Garder un nombre sans modifier une variable

Notre service s'arrêtait après une seule demande. Pour en traiter une autre,
il lui suffit de rappeler une fonction qui attend un message. Et pour
conserver le compteur, nous passons sa valeur à cet appel suivant !

C'est l'accumulateur d'AP1, avec une différence : la prochaine donnée vient
de la boîte aux lettres, et non du reste d'une liste. L'appel récursif est
la dernière opération de chaque branche ; cette récursion terminale
n'empile pas les appels précédents. Pendant un `receive` en attente, le
processus ne fait pas tourner une boucle de calcul à vide.

Le tableau ci-dessous définit notre protocole. Les valeurs initiales et les
ajouts sont des nombres ; un ajout négatif est donc permis dans ce premier
laboratoire. AP7 restreindra le contrat aux contributions entières positives
ou nulles. Chaque requête contient un client et une référence, et chaque
réponse indique la valeur du compteur après cette requête.

| Message reçu | Réponse | État du prochain appel |
| --- | --- | --- |
| `{:get, client, ref}` | `{:result, ref, value}` | `value` |
| `{:add, client, ref, amount}` | `{:result, ref, value + amount}` | `value + amount` |
| `:stop` | aucune | la fonction se termine |

La branche `:get` est fournie. Ajoutez `:add`, puis `:stop`. Pour finir,
écrivez les deux fonctions publiques : elles créent une référence, envoient
le message avec `self()`, puis appellent votre fonction `Replies.await/2`.
Elles rendent donc `{:ok, value}` ou `{:error, :timeout}`.

`start/1` est fourni : le nombre initial est capturé par la fonction anonyme
et transmis à `loop/1`. Il n'y a ni variable globale ni dictionnaire de
processus : **l'état est l'argument de la boucle**.

```elixir
defmodule Counter do
  def start(initial), do: spawn(fn -> loop(initial) end)

  def get(_pid, _timeout), do: raise("Envoyez une demande de lecture")
  def add(_pid, _amount, _timeout), do: raise("Envoyez une demande d'ajout")

  defp loop(value) do
    receive do
      {:get, client, ref} ->
        send(client, {:result, ref, value})
        loop(value)

      {:add, _client, _ref, _amount} ->
        raise "Calculez la valeur suivante, répondez, puis rappelez loop"

      :stop ->
        raise "Terminez sans rappeler loop"
    end
  end
end
```

```elixir
counter = Counter.start(10)

try do
  assert Counter.get(counter, 1_000) == {:ok, 10}
  assert Counter.add(counter, 3, 1_000) == {:ok, 13}
  assert Counter.add(counter, -2, 1_000) == {:ok, 11}
  assert Counter.get(counter, 1_000) == {:ok, 11}
  assert Counter.get(counter, 1_000) == {:ok, 11}
after
  Process.exit(counter, :kill)
end
```

Que se passe-t-il si `:add` répond avec la bonne valeur, mais rappelle
`loop(value)` ? Pourquoi le test suivant peut-il détecter ce souci ?
Ajoutez un test avec un ajout nul, puis vérifiez que deux processus
créés avec le même module gardent des valeurs indépendantes.

```elixir
left = Counter.start(0)
right = Counter.start(100)

try do
  assert Counter.add(left, 5, 1_000) == {:ok, 5}
  assert Counter.get(right, 1_000) == {:ok, 100}
  assert Counter.get(left, 1_000) == {:ok, 5}
after
  Process.exit(left, :kill)
  Process.exit(right, :kill)
end
```

Le module décrit le comportement ; chaque appel à `start/1` crée une
nouvelle exécution avec son propre état. Pour l'instant, tous les clients
de nos tests sont le processus qui évalue la cellule. La cellule suivante
fait écrire deux autres processus au même compteur.

La compréhension `for` d'AP4 lance un client par montant et garde leurs
PID dans `clients`. Chaque client ajoute, puis signale à l'observateur ce
qu'il a obtenu. Prévoyez la valeur finale, puis les deux valeurs
intermédiaires possibles. Nous attendons les accusés de réception des
clients avant de lire le compteur : pas besoin d'attendre un nombre
arbitraire de millisecondes.

```elixir
counter = Counter.start(0)
observer = self()
tag = make_ref()

clients =
  for amount <- [2, 5] do
    spawn(fn ->
      result = Counter.add(counter, amount, 1_000)
      send(observer, {:done, tag, amount, result})
    end)
  end

try do
  assert_receive {:done, ^tag, 2, {:ok, from_two}}, 1_000
  assert_receive {:done, ^tag, 5, {:ok, from_five}}, 1_000
  assert {from_two, from_five} in [{2, 7}, {7, 5}]
  assert Counter.get(counter, 1_000) == {:ok, 7}
after
  Enum.each(clients, fn pid -> Process.exit(pid, :kill) end)
  Process.exit(counter, :kill)
end
```

Le compteur traite une demande à la fois. L'ordre des deux clients peut
changer, mais chacun ajoute à la dernière valeur conservée par la boucle.
Nous n'avons pas demandé aux clients de lire, puis de réécrire séparément
un nombre : ce sont les commandes `:add` qui sont traitées par le compteur.
Cette distinction nous servira pour les exercices de la prochaine séance.

## 5 · Comment apprendre qu'un processus s'est arrêté ?

L'absence de réponse ne dit pas si le service est occupé, bloqué ou déjà
terminé. Un *monitor* permet d'observer sa fin : `Process.monitor(pid)`
rend une référence, puis nous recevrons, comme n'importe quel message,
`{:DOWN, ref, :process, pid, reason}` quand il se termine. La raison
dit comment : `:normal` pour une fonction qui s'est terminée sans erreur,
autre chose pour une panne. `Process.alive?(pid)` rend `true` tant que le
processus existe.

Voici d'abord l'arrêt demandé par notre protocole. Le processus termine
sa fonction normalement, donc la raison attendue est `:normal`.

```elixir
counter = Counter.start(0)
monitor = Process.monitor(counter)

try do
  send(counter, :stop)
  assert_receive {:DOWN, ^monitor, :process, ^counter, :normal}, 1_000
  assert Process.alive?(counter) == false
after
  Process.demonitor(monitor, [:flush])
  Process.exit(counter, :kill)
end
```

Nous allons maintenant provoquer une *panne*, c-à-d. une terminaison
anormale, dans un processus distinct de celui de Livebook. `spawn_monitor`
crée le processus et le monitor ensemble : nous ne risquons pas de
commencer à l'observer après sa fin. `exit(raison)` termine le processus
qui l'exécute avec cette raison ; `exit(:simulated_failure)` est donc le
point de panne volontaire de cet essai.

Complétez `Failures.observe/0` : lancez cette fonction avec `spawn_monitor`,
recevez le message `:DOWN` correspondant et rendez `{:stopped, reason}`.
Prévoyez un délai de 1 000 ms qui rend `{:error, :timeout}`.
Utilisez `try ... after` pour nettoyer le monitor et le processus,
suivant le modèle de la cellule précédente.

```elixir
defmodule Failures do
  def observe(), do: raise("Lancez et observez la panne dans un autre processus")
end
```

```elixir
assert Failures.observe() == {:stopped, :simulated_failure}
assert 6 * 7 == 42
```

Notre cellule continue après la panne, car le monitor nous informe sans
propager cet arrêt. Un *lien* entre processus a un autre rôle : il peut
propager une terminaison anormale. Nous n'utiliserons donc pas `spawn_link`
pour cet essai de panne ; les superviseurs utiliseront les liens pour
prendre en charge la vie de leurs enfants.

`Process.demonitor(monitor, [:flush])` retire l'observation et un éventuel
message `:DOWN` encore en attente. Vous pouvez conserver ce nettoyage fourni.
Le monitor informe ; il ne relance rien à lui seul.

Pour le vérifier, utilisons un PID dont nous avons observé la fin. Prévoyez
le résultat de `send`, puis celui de `Counter.get`.

```elixir
counter = Counter.start(9)
monitor = Process.monitor(counter)

try do
  send(counter, :stop)
  assert_receive {:DOWN, ^monitor, :process, ^counter, :normal}, 1_000
  assert send(counter, :hello) == :hello
  assert Counter.get(counter, 10) == {:error, :timeout}
after
  Process.demonitor(monitor, [:flush])
  Process.exit(counter, :kill)
end
```

Envoyer à ce PID mort ne l'a pas ressuscité et n'a pas confirmé une livraison.
Si nous voulons relancer un service, quelqu'un doit prendre cette décision,
connaître son démarrage et gérer le nouveau PID. C'est le rôle que nous
allons confier à un superviseur.

## 6 · Qui remet le compteur en marche ?

OTP, la bibliothèque standard d'Erlang pour les systèmes qui durent,
fournit des briques de processus et des *superviseurs* : un superviseur
est un processus dont le rôle est de démarrer d'autres processus, ses
enfants, et de les relancer quand ils tombent. Nous découvrirons Agent et
GenServer en AP7 ; pour cette démonstration, un `Agent` fourni représente
simplement un processus qui conserve une valeur. `Agent.get` la lit,
`Agent.update` la remplace en appliquant une fonction.

Un superviseur reçoit la manière de démarrer ses enfants, et
`Supervisor.start_link` rend `{:ok, pid}` une fois ceux-ci lancés. Le
`link` de son nom est le lien évoqué plus haut : il relie le superviseur à
ses enfants, et c'est par lui qu'il apprend leur fin. Ici la stratégie
`:one_for_one` relance seulement l'enfant qui tombe. L'enfant est permanent
par défaut : le superviseur le relance lorsqu'il se termine, dans la limite
de son budget de redémarrages. Il ne répare pas une erreur qui se reproduit
indéfiniment.

La cellule ci-dessous est une démonstration fournie, à lire ensemble.
La panne est provoquée par `Process.exit(first, :kill)`, et le monitor
rapporte alors la raison `:killed`. Prévoyez le nombre stocké et le PID
après la panne. Notre fonction de démarrage envoie une notification à
l'observateur : nous pouvons ainsi attendre le redémarrage sans `sleep`
ni boucle de sondage.

```elixir
observer = self()
tag = make_ref()

child = %{
  id: :counter,
  start: {Agent, :start_link, [fn ->
    send(observer, {:started, tag, self()})
    0
  end]}
}

{:ok, supervisor} = Supervisor.start_link([child], strategy: :one_for_one)

try do
  assert_receive {:started, ^tag, first}, 1_000
  Agent.update(first, fn value -> value + 7 end)
  assert Agent.get(first, fn value -> value end) == 7
  monitor = Process.monitor(first)

  try do
    Process.exit(first, :kill)
    assert_receive {:DOWN, ^monitor, :process, ^first, :killed}, 1_000
    assert_receive {:started, ^tag, second}, 1_000
    assert second != first
    assert Agent.get(second, fn value -> value end) == 0
  after
    Process.demonitor(monitor, [:flush])
  end
after
  Supervisor.stop(supervisor)
end
```

Le processus a redémarré avec un nouveau PID, en reprenant la valeur initiale
`0`. La supervision remet une brique en service ; elle ne sauvegarde pas son
ancien état. Notre compteur en mémoire disparaît avec son processus.

Dans la description de l'enfant, `start` contient le module, le nom de la
fonction et la liste des arguments à utiliser au démarrage. Le superviseur
peut refaire cet appel. La notification `:started` est seulement notre moyen
d'observer la démonstration ; un service n'a pas à connaître Livebook.

Gardez trois phrases dans votre carnet : où vit le nombre, ce que garantit
une réponse, et ce que le superviseur reconstruit après une panne.

## 7 · Sortir du carnet avec Mix

Nous allons terminer dans le terminal et l'éditeur préparés avant le cours.
Nous écrivons toujours de l'Elixir ; le résultat sera une application OTP,
c'est-à-dire une unité que la VM sait charger et démarrer avec son arbre de
supervision. Une application peut contenir de nombreux processus ; elle
n'est pas elle-même un processus.

Dans un dossier où vous conservez vos exercices, lancez ces commandes.
L'option `--sup` demande à Mix de générer, en plus du projet, un module
Application et un superviseur sans enfant :

```sh
elixir --version
mix --version
mix new process_lab --sup
cd process_lab
mix test
```

Ouvrez le dossier `process_lab` dans VS Code, ou dans Gram si vous avez
choisi cette option. Les fichiers qui nous intéressent sont les suivants :

| Fichier | Ce que nous y trouvons |
| --- | --- |
| `mix.exs` | le nom, la version, les dépendances et le démarrage du projet |
| `lib/process_lab/application.ex` | le point de départ de l'arbre de supervision |
| `lib/process_lab.ex` | un premier module, où nous pourrons écrire des fonctions |
| `test/` | les vérifications que `mix test` exécute |

Dans `application.ex`, remplacez la liste `children` par
`children = [{Agent, fn -> 0 end}]`. Le module complet devient :

```text
defmodule ProcessLab.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [{Agent, fn -> 0 end}]
    Supervisor.start_link(children, strategy: :one_for_one, name: ProcessLab.Supervisor)
  end
end
```

L'Agent fournit sa propre description d'enfant ; la notation est donc plus
courte que celle de notre démonstration. `use Application` et `start/2` sont
le contrat généré par Mix : la VM appelle `start/2` au démarrage de
l'application, et `@impl true` signale que cette fonction répond à ce
contrat. Nous en lirons davantage au besoin. Pour le moment, nous avons
simplement ajouté une brique à sa liste d'enfants.

Dans le terminal, formatez, vérifiez, puis ouvrez une console qui démarre
l'application :

```sh
mix format
mix test
iex -S mix
```

Sous PowerShell, si la dernière commande ne démarre pas IEx, utilisez
`iex.bat -S mix`. Dans IEx, saisissez les lignes après `iex>` ; la première
affiche la liste des enfants du superviseur, avec un numéro de PID qui
change à chaque démarrage, et le motif retient le PID de l'Agent dans `counter` :

```iex
iex> [{Agent, counter, :worker, _}] = Supervisor.which_children(ProcessLab.Supervisor)
[{Agent, #PID<0.164.0>, :worker, [Agent]}]
iex> Agent.get(counter, fn value -> value end)
0
iex> Agent.update(counter, fn value -> value + 3 end)
:ok
iex> Agent.get(counter, fn value -> value end)
3
```

Nous avons interrogé un processus démarré par notre application, sans cellule
Livebook. Quittez avec Ctrl+C deux fois, relancez `iex -S mix`, retrouvez le
PID et lisez la valeur. Le retour à `0` a la même cause que tout à l'heure :
rien n'a écrit ce nombre sur disque.

**Pensez à sauvegarder** votre carnet sous `AP6/ap6-processus.livemd` et à
conserver le dossier `process_lab`. En AP7, nous repartirons de squelettes
Mix pour explorer l'état, Agent et GenServer. Les consignes de rendu seront
précisées en cours ; les anciennes consignes de mini-blockchain ne concernent
pas ce parcours.

Pour retrouver les détails : [processus et messages](https://hexdocs.pm/elixir/processes.html),
[Process](https://hexdocs.pm/elixir/Process.html),
[Supervisor](https://hexdocs.pm/elixir/Supervisor.html) et
[applications Mix](https://hexdocs.pm/elixir/mix-otp/introduction-to-mix.html).
Le chapitre 9 du [poly](https://ap-2026.noce.fr/poly/poly-elixir.pdf) reprend les formes de ce carnet, à consulter
quand un bout de syntaxe vous manque.

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