<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Railway on Ocarina — précis</title>
    <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/series/railway/</link>
    <description>Recent content in Railway on Ocarina — précis</description>
    <generator>Hugo</generator>
    <language>fr-FR</language>
    <lastBuildDate>Thu, 28 May 2026 21:02:53 +0200</lastBuildDate>
    <atom:link href="https://mojo-molotov.github.io/from-ocarina-to-igor/fr/series/railway/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>02.03.01 — Le type Result[T]</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/01-result/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/01-result/</guid>
      <description>&lt;h1 id=&#34;020301le-type-resultt&#34;&gt;02.03.01 — Le type &lt;code&gt;Result[T]&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020301le-type-resultt&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/railway/result.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/railway/result.py&lt;/code&gt;&lt;/a&gt; — &lt;strong&gt;zéro dépendance&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; dataclasses &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; dataclass, field&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; typing &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; TypeGuard, final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_BaseResult&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;Base class ensuring both Ok and Fail have error attribute for type narrowing.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Ok&lt;/span&gt;[T](_BaseResult):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value: T&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@final&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@dataclass(frozen&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;True&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Fail&lt;/span&gt;(_BaseResult):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; field(default_factory&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;lambda&lt;/span&gt;: Exception(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Unknown error&amp;#34;&lt;/span&gt;))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Result[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Ok[T] &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; Fail&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_ok&lt;/span&gt;[T](result: Result[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TypeGuard[Ok[T]]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(result, Ok)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;is_fail&lt;/span&gt;[T](result: Result[T]) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; TypeGuard[Fail]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;isinstance&lt;/span&gt;(result, Fail)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;six-décisions-de-design-à-décortiquer&#34;&gt;Six décisions de design à décortiquer&lt;a class=&#34;anchor&#34; href=&#34;#six-d%c3%a9cisions-de-design-%c3%a0-d%c3%a9cortiquer&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-_baseresult-comme-parent-commun&#34;&gt;1. &lt;code&gt;_BaseResult&lt;/code&gt; comme parent commun&lt;a class=&#34;anchor&#34; href=&#34;#1-_baseresult-comme-parent-commun&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;_BaseResult&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error: Exception &lt;span style=&#34;color:#ff79c6&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But unique : permettre à &lt;em&gt;mypy&lt;/em&gt; d&amp;rsquo;&lt;strong&gt;accéder à &lt;code&gt;result.error&lt;/code&gt; sans narrowing préalable&lt;/strong&gt;. Sans cette base, on devrait écrire :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.02 — La machine à états du builder</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/02-action-chain-states/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/02-action-chain-states/</guid>
      <description>&lt;h1 id=&#34;020302la-machine-à-états-du-builder&#34;&gt;02.03.02 — La machine à états du builder&lt;a class=&#34;anchor&#34; href=&#34;#020302la-machine-%c3%a0-%c3%a9tats-du-builder&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/internals/action_chain.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/internals/action_chain.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;C&amp;rsquo;est ici qu&amp;rsquo;est implémentée &lt;em&gt;toute&lt;/em&gt; la mécanique ROP : le passage par quatre états successifs typés, qui interdit littéralement (au sens du &lt;em&gt;type-checker&lt;/em&gt;) toute fantaisie syntaxique. Le DSL est aussi son propre système immunitaire.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;vue-densemble&#34;&gt;Vue d&amp;rsquo;ensemble&lt;a class=&#34;anchor&#34; href=&#34;#vue-densemble&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;                      ┌─────────────────┐&#xA;   start(action)  →   │  ActionStart[T] │     un Action[T] = Thunk[Result[T]]&#xA;                      └────────┬────────┘&#xA;                               │ .failure(failure_handler)&#xA;                               ▼&#xA;                      ┌─────────────────┐&#xA;                      │ ActionFailure[T]│&#xA;                      └────────┬────────┘&#xA;                               │ .success(success_handler)&#xA;                               ▼&#xA;                      ┌─────────────────┐&#xA;                      │ ActionSuccess[T]│&#xA;                      └────────┬────────┘&#xA;                               │ .execute()   ── exécute action(), fire le handler&#xA;                               ▼&#xA;                      ┌─────────────────┐&#xA;                      │ ActionChain[T]  │     contient (has_failed, result)&#xA;                      └──────┬──────────┘&#xA;                             │ .then(next_action_or_start)&#xA;              ┌──────────────┴──────────────┐&#xA;              │                             │&#xA;   has_failed=True                   has_failed=False&#xA;              │                             │&#xA;              ▼                             ▼&#xA;   ┌──────────────────┐         ┌──────────────────┐&#xA;   │ NeutralAction-   │         │ ActionStart[T]   │  → cycle recommence&#xA;   │ Start[T]         │         │  (l&amp;#39;action suiv. │&#xA;   │ (rail d&amp;#39;échec :  │         │   sera exécutée) │&#xA;   │  toute la chaîne │         └──────────────────┘&#xA;   │  devient no-op,  │&#xA;   │ tout en gardant  │&#xA;   │ l&amp;#39;API fluide)    │&#xA;   └──────────────────┘&#xA;              │&#xA;              ▼ (.failure → .success → .execute, tout en no-op)&#xA;   ┌──────────────────┐&#xA;   │  ActionChain[T]  │   has_failed=True, result=&amp;lt;le Fail accumulé&amp;gt;&#xA;   └──────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;les-types-action-failurehandler-succhandler&#34;&gt;Les types &lt;code&gt;Action&lt;/code&gt;, &lt;code&gt;FailureHandler&lt;/code&gt;, &lt;code&gt;SuccHandler&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#les-types-action-failurehandler-succhandler&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; Action[T]        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Thunk[Result[T]]      &lt;span style=&#34;color:#6272a4&#34;&gt;# () -&amp;gt; Result[T]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; FailureHandler   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Callable[[Exception], &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; SuccHandler      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; Effect                &lt;span style=&#34;color:#6272a4&#34;&gt;# () -&amp;gt; None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;Action[T]&lt;/code&gt; est un &lt;code&gt;Thunk&lt;/code&gt;&lt;/strong&gt;. Cela veut dire qu&amp;rsquo;au moment où &lt;code&gt;ActionStart(action)&lt;/code&gt; est appelé, &lt;strong&gt;rien n&amp;rsquo;est exécuté&lt;/strong&gt;. L&amp;rsquo;action est juste capturée.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;FailureHandler&lt;/code&gt; reçoit l&amp;rsquo;exception&lt;/strong&gt;, mais ne reçoit &lt;strong&gt;pas&lt;/strong&gt; le &lt;code&gt;Fail&lt;/code&gt; ni le &lt;code&gt;Result&lt;/code&gt;. C&amp;rsquo;est volontaire : 99% des handlers veulent tracer l&amp;rsquo;exception, prendre un screenshot, et c&amp;rsquo;est tout.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;SuccHandler&lt;/code&gt; est un &lt;code&gt;Effect&lt;/code&gt;&lt;/strong&gt; (sans argument). Idem : un handler de succès log un message statique, fait un screenshot, pas besoin du résultat.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;typestate-pattern-pourquoi-on-ne-peut-pas-se-tromper&#34;&gt;Typestate Pattern : pourquoi on ne peut pas se tromper&lt;a class=&#34;anchor&#34; href=&#34;#typestate-pattern-pourquoi-on-ne-peut-pas-se-tromper&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;L&amp;rsquo;enchaînement est &lt;strong&gt;strictement linéaire&lt;/strong&gt;. Chaque méthode renvoie un type différent, qui n&amp;rsquo;expose que la &lt;em&gt;suite logique&lt;/em&gt; de l&amp;rsquo;enchaînement :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.03 — Le rail d&#39;échec : NeutralAction*</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/03-neutral-rail/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/03-neutral-rail/</guid>
      <description>&lt;h1 id=&#34;020303le-rail-déchecneutralaction&#34;&gt;02.03.03 — Le rail d&amp;rsquo;échec : &lt;code&gt;NeutralAction*&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020303le-rail-d%c3%a9checneutralaction&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Cas particulier du builder : que se passe-t-il quand on &lt;em&gt;enchaîne&lt;/em&gt; une action sur un &lt;code&gt;ActionChain&lt;/code&gt; qui a déjà échoué ?&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;pourquoi-des-classes-neutral-plutôt-quun-if-côté-utilisateur&#34;&gt;Pourquoi des classes &lt;em&gt;Neutral&lt;/em&gt; plutôt qu&amp;rsquo;un &lt;code&gt;if&lt;/code&gt; côté utilisateur&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-des-classes-neutral-plut%c3%b4t-quun-if-c%c3%b4t%c3%a9-utilisateur&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Lorsqu&amp;rsquo;on enchaîne une action sur un &lt;code&gt;ActionChain&lt;/code&gt; qui a déjà échoué, on bascule dans trois classes &lt;em&gt;Neutral&lt;/em&gt; qui acceptent la même API, mais ne font rien.&lt;/p&gt;&#xA;&lt;p&gt;Imaginons que le DSL n&amp;rsquo;ait pas cette mécanique.&lt;br&gt;&#xA;Le code utilisateur ressemblerait à :&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.04 — chain_actions et le ChainRunner</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/04-chain-actions-fold/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/04-chain-actions-fold/</guid>
      <description>&lt;h1 id=&#34;020304chain_actions-et-le-chainrunner&#34;&gt;02.03.04 — &lt;code&gt;chain_actions&lt;/code&gt; et le &lt;code&gt;ChainRunner&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020304chain_actions-et-le-chainrunner&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/chain_actions.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/chain_actions.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;C&amp;rsquo;est la primitive qui rend le DSL « &lt;em&gt;plat&lt;/em&gt; ». Sans elle, un scénario à N pas serait un escalier de &lt;code&gt;.then(...).failure(...).success(...).execute()&lt;/code&gt;. Avec elle, c&amp;rsquo;est une liste.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;parenthesis-hell&#34;&gt;« &lt;em&gt;Parenthesis hell&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#parenthesis-hell&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Solution :&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Flat, readable syntax&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;runner &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; chain_actions(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action1&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h1)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h2),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action2&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h3)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h4),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action3&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;failure(h5)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;success(h6)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; runner&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;run()  &lt;span style=&#34;color:#6272a4&#34;&gt;# Execute when ready&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Benefits :&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Lazy evaluation&lt;/strong&gt; : Build chain without executing&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Flat syntax&lt;/strong&gt; : No deep nesting&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Automatic short-circuiting&lt;/strong&gt; : Stops on first failure&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt; : ChainRunner is a value&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&lt;p&gt;&lt;em&gt;(Extrait de docstring.)&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.05 — create_act et ses hooks</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/05-create-act-hooks/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/05-create-act-hooks/</guid>
      <description>&lt;h1 id=&#34;020305create_act-et-ses-hooks&#34;&gt;02.03.05 — &lt;code&gt;create_act&lt;/code&gt; et ses hooks&lt;a class=&#34;anchor&#34; href=&#34;#020305create_act-et-ses-hooks&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/constructors/create_act.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/constructors/create_act.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Cette fonction est la &lt;strong&gt;primitive de bas niveau&lt;/strong&gt; qui crée un constructeur de &lt;em&gt;pas de test&lt;/em&gt;. Elle est appelée par l&amp;rsquo;utilisateur &lt;strong&gt;une fois&lt;/strong&gt;, pour créer un verbe &lt;code&gt;act&lt;/code&gt; propre au projet. Selon la complexité du projet, on peut envisager plusieurs &lt;code&gt;act&lt;/code&gt; distincts avec des hooks différents, mais aucun cas d&amp;rsquo;usage encore observé pour ça, donc on préconise de n&amp;rsquo;en créer &lt;strong&gt;qu&amp;rsquo;un seul&lt;/strong&gt;. Ce n&amp;rsquo;est pas un singleton pour autant : c&amp;rsquo;est &lt;em&gt;juste&lt;/em&gt; une convention.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.06 — drive_page</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/06-drive-page/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/06-drive-page/</guid>
      <description>&lt;h1 id=&#34;020306drive_page&#34;&gt;02.03.06 — &lt;code&gt;drive_page&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020306drive_page&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/opinionated/dsl/drive_page.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/opinionated/dsl/drive_page.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;code&#34;&gt;Code&lt;a class=&#34;anchor&#34; href=&#34;#code&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;drive_page&lt;/span&gt;(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    first: ActionSuccess[TPOM], &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;rest: ActionSuccess[TPOM]&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; ChainRunner[TPOM]:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; chain_actions(first, &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;rest)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est &lt;strong&gt;exactement&lt;/strong&gt; &lt;code&gt;chain_actions&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;pourquoi-cet-alias&#34;&gt;Pourquoi cet alias ?&lt;a class=&#34;anchor&#34; href=&#34;#pourquoi-cet-alias&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-sémantique-je-prends-le-contrôle-d&#34;&gt;1. Sémantique : « &lt;em&gt;je prends le contrôle d&amp;rsquo;une page&lt;/em&gt; »&lt;a class=&#34;anchor&#34; href=&#34;#1-s%c3%a9mantique-je-prends-le-contr%c3%b4le-d&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Citation du Holy Book (&lt;em&gt;Premiers scénarios&lt;/em&gt;) :&lt;/p&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;&lt;code&gt;drive_page&lt;/code&gt; exprime que l&amp;rsquo;on prend le contrôle d&amp;rsquo;&lt;em&gt;une&lt;/em&gt; page.&#xA;Toute &lt;em&gt;transition&lt;/em&gt; devient explicite par l&amp;rsquo;ouverture d&amp;rsquo;un nouveau &lt;code&gt;drive_page&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; [&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    drive_page(&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, open_homepage)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, verify_homepage)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_homepage, click_cta)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),                                          &lt;span style=&#34;color:#6272a4&#34;&gt;# ⬅ fermeture du contrôle de la homepage&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    drive_page(                                 &lt;span style=&#34;color:#6272a4&#34;&gt;# ⬅ ouverture du contrôle de la page suivante&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        act(on_target_page, verify_target_page)&lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-discipline&#34;&gt;2. Discipline&lt;a class=&#34;anchor&#34; href=&#34;#2-discipline&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;p&gt;Mélanger des actes sur deux POMs différents dans un même &lt;code&gt;drive_page&lt;/code&gt; devient une erreur &lt;em&gt;mypy&lt;/em&gt; (cf. &lt;a href=&#34;https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/02-action-chain-states/&#34;&gt;&lt;code&gt;02-action-chain-states.md&lt;/code&gt;&lt;/a&gt;, section « &lt;em&gt;narrowing par &lt;code&gt;act()&lt;/code&gt;&lt;/em&gt; »). Cela force concrètement à respecter la sémantique.&lt;/p&gt;</description>
    </item>
    <item>
      <title>02.03.07 — match_page / when</title>
      <link>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/07-match-page-when/</link>
      <pubDate>Wed, 20 May 2026 00:00:00 +0000</pubDate>
      <guid>https://mojo-molotov.github.io/from-ocarina-to-igor/fr/02-ocarina/03-railway/07-match-page-when/</guid>
      <description>&lt;h1 id=&#34;020307match_page-when&#34;&gt;02.03.07 — &lt;code&gt;match_page&lt;/code&gt; / &lt;code&gt;when&lt;/code&gt;&lt;a class=&#34;anchor&#34; href=&#34;#020307match_page-when&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h1&gt;&#xA;&lt;blockquote class=&#39;book-hint &#39;&gt;&#xA;&lt;p&gt;Fichier source : &lt;a href=&#34;https://github.com/mojo-molotov/ocarina/blob/main/src/ocarina/dsl/testing_with_railway/match_page.py&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;&lt;code&gt;src/ocarina/dsl/testing_with_railway/match_page.py&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Ajouté &lt;strong&gt;après coup&lt;/strong&gt; au framework. Le Holy Book précise : « &lt;em&gt;&lt;code&gt;match_page&lt;/code&gt; et &lt;code&gt;when&lt;/code&gt; ont été ajoutés après coup, l&amp;rsquo;Igoristan était tellement aléatoire que le cas d&amp;rsquo;usage s&amp;rsquo;est imposé de lui-même. Leur implémentation a été simple, preuve de la flexibilité de la grammaire : d&amp;rsquo;autres structures analogues pourraient très bien suivre.&lt;/em&gt; »&lt;/p&gt;&#xA;&lt;/blockquote&gt;&lt;h2 id=&#34;le-problème&#34;&gt;Le problème&lt;a class=&#34;anchor&#34; href=&#34;#le-probl%c3%a8me&#34; data-pagefind-ignore=&#34;all&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&#xA;&lt;p&gt;Certaines pages d&amp;rsquo;une application peuvent être rendues différemment :&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
