<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://lachrist.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://lachrist.github.io/" rel="alternate" type="text/html" /><updated>2026-02-14T22:02:31+00:00</updated><id>https://lachrist.github.io/feed.xml</id><title type="html">lachrist</title><subtitle>Laurent Christophe&apos;s personal page</subtitle><author><name>Laurent Christophe</name></author><entry><title type="html">Let’s Reify Effects</title><link href="https://lachrist.github.io/blog/2023/05/14/effect/" rel="alternate" type="text/html" title="Let’s Reify Effects" /><published>2023-05-14T00:00:00+00:00</published><updated>2023-05-14T00:00:00+00:00</updated><id>https://lachrist.github.io/blog/2023/05/14/effect</id><content type="html" xml:base="https://lachrist.github.io/blog/2023/05/14/effect/"><![CDATA[<p>This is my attempt at demystifying effect systems of pure functional languages and how to implement them in impure imperative languages.</p>

<h2 id="function-purity">Function Purity</h2>

<p>To tell whether a function written in an impure language is pure, just ask yourself whether it can be replaced by a (possibly infinite) mapping from its arguments to its results – nice talk <a href="https://youtu.be/3n17wHe5wEw?t=416">here</a>. This corresponds to the mathematical definition of a function. Effects are what make functions impure:</p>
<ul>
  <li>Input/Output actions.</li>
  <li>Never returning.</li>
  <li>Crashing the program.</li>
  <li>Throwing an exception.</li>
  <li>In-memory mutation.</li>
</ul>

<p>Pros of pure functions:</p>
<ul>
  <li>Easier to test – e.g., no mock db, no file setup.</li>
  <li>Type signature offers better documentation.</li>
  <li>No race condition means easier concurrency.</li>
  <li>Can be memoized.</li>
  <li>Easier to reason about (somehow subjective).</li>
</ul>

<p>Pros of impure functions:</p>
<ul>
  <li>Can actually do something useful by performing I/O actions.</li>
</ul>

<p>So we want to write programs with pure functions, but we also want to produce programs that have effects on the real world. Because effects are contagious, a function becomes impure when it calls an impure function. That means that if we want to maximize the amount of logic implemented by pure functions, we need to <a href="https://blog.ploeh.dk/2017/02/02/dependency-rejection/">reject effects</a> to the edge of our programs. This architecture has many names: functional core and imperative shell, ports and adapters, or hexagonal – nice talk <a href="https://youtu.be/US8QG9I1XW0">here</a>.</p>

<h2 id="supporting-output-actions">Supporting Output Actions</h2>

<p>One approach to move logic inside the functional core is to <a href="https://en.wikipedia.org/wiki/Reification_%28computer_science%29">reify</a> effects. That is, impure functions are turned into pure functions by making them compute <em>descriptions</em> of their effects rather than letting them directly carry them. These reified effects still need to be interpreted to do anything useful.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Runtime [impure] //</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">writeFile</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:fs/promises</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">run</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">effect</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">writeFile</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">encoding</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">unknown effect</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// Prelude [pure] //</span>
<span class="kd">const</span> <span class="nx">writeFileEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">,</span>
<span class="p">});</span>

<span class="c1">// Main [pure] //</span>
<span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">file.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">foo</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">);</span>

<span class="c1">// Runtime [impure] //</span>
<span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">main</span><span class="p">);</span>
</code></pre></div></div>

<p>At the extreme, every single effect (besides running forever or crashing) carried by the program is reified that way. We then speak about <em>effect system</em>. Effect systems can be made generic and reusable. This is what pure functional languages usually do to support IO actions:</p>
<ul>
  <li>In <a href="https://www.haskell.org/">Haskell</a>, it is called <a href="https://www.haskell.org/tutorial/io.html">IO</a>.</li>
  <li>In <a href="https://elm-lang.org/">Elm</a>, it is called <a href="https://package.elm-lang.org/packages/elm/core/latest/">Task</a>.</li>
  <li>In <a href="https://www.purescript.org">Purescript</a>, it is called <a href="https://book.purescript.org/chapter8.html">Effect</a>.</li>
</ul>

<h2 id="combining-output-actions">Combining Output Actions</h2>

<p>We can add both concurrent and sequential composition to our effect system:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Runtime [impure] //</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">writeFile</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:fs/promises</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">run</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">effect</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">writeFile</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">encoding</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">concurrent</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span><span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">left</span><span class="p">),</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">right</span><span class="p">)]);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">sequence</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">first</span><span class="p">);</span>
    <span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">second</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">unknown effect</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// Prelude [pure] //</span>
<span class="kd">const</span> <span class="nx">writeFileEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">concurrentEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">concurrent</span><span class="dl">"</span><span class="p">,</span> <span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">sequenceEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">first</span><span class="p">,</span> <span class="nx">second</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">sequence</span><span class="dl">"</span><span class="p">,</span> <span class="nx">first</span><span class="p">,</span> <span class="nx">second</span><span class="p">,</span>
<span class="p">});</span>

<span class="c1">// Main [pure] //</span>
<span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="nx">sequenceEffect</span><span class="p">(</span>
  <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">file1.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">foo</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
  <span class="nx">concurrentEffect</span><span class="p">(</span>
    <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">file2.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">bar</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
    <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">file3.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">qux</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">);</span>

<span class="c1">// Runtime [impure] //</span>
<span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">main</span><span class="p">);</span>
</code></pre></div></div>

<h2 id="supporting-input-actions-with-abstract-machines">Supporting Input Actions with Abstract Machines</h2>

<p>Up until now, our effect system could only express entirely static programs that do the same thing every time they are executed. To make our program more dynamic we need to retrieve the values returned by our effects and somehow plug them back to the functional core.</p>

<p>One idea is to turn our main into a function that accepts an input and returns an effect. Then we repeatedly call this function with whatever input is available. To help our main function to make sense of the input we should also bookkeep a state.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Runtime [impure] //</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">readFile</span><span class="p">,</span> <span class="nx">writeFile</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:fs/promises</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">run</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">effect</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="nx">writeFile</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">encoding</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">readFile</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="nx">readFile</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">encoding</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">concurrent</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span><span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">left</span><span class="p">),</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">right</span><span class="p">)]);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">unknown effect</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// Prelude [pure] //</span>
<span class="kd">const</span> <span class="nx">writeFileEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">readFileEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">readFile</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">concurrentEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">concurrent</span><span class="dl">"</span><span class="p">,</span> <span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">,</span>
<span class="p">});</span>

<span class="c1">// Main [pure] //</span>
<span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">initial</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">state</span><span class="p">:</span> <span class="dl">"</span><span class="s2">reading</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">effect</span><span class="p">:</span> <span class="nx">readFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">file.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">},</span>
  <span class="na">step</span><span class="p">:</span> <span class="p">({</span><span class="nx">state</span><span class="p">,</span> <span class="nx">input</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">state</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">reading</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="p">{</span>
        <span class="na">state</span><span class="p">:</span> <span class="dl">"</span><span class="s2">writing</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">effect</span><span class="p">:</span> <span class="nx">concurrentEffect</span><span class="p">(</span>
          <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">copy1.txt</span><span class="dl">"</span><span class="p">,</span> <span class="nx">input</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
          <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">copy2.txt</span><span class="dl">"</span><span class="p">,</span> <span class="nx">input</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
        <span class="p">),</span>
      <span class="p">};</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">state</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">writing</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="p">{</span>
        <span class="na">state</span><span class="p">:</span> <span class="dl">"</span><span class="s2">final</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">effect</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
      <span class="p">};</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">invalid state</span><span class="dl">"</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">},</span>
<span class="p">};</span>

<span class="c1">// Runtime [impure] //</span>
<span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">step</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">main</span><span class="p">;</span>
  <span class="kd">let</span> <span class="p">{</span> <span class="na">initial</span><span class="p">:</span> <span class="p">{</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">effect</span> <span class="p">}</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">main</span><span class="p">;</span>
  <span class="k">while</span> <span class="p">(</span><span class="nx">effect</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">({</span> <span class="nx">state</span><span class="p">,</span> <span class="nx">effect</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">step</span><span class="p">({</span> <span class="nx">state</span><span class="p">,</span> <span class="na">input</span><span class="p">:</span> <span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">)</span> <span class="p">}));</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Our <code class="language-plaintext highlighter-rouge">main</code> effectively encodes an <a href="https://en.wikipedia.org/wiki/Abstract_machine">abstract machine</a>: an initial state, a final state, and a state transition function. By also reifying the state, abstract machines facilitate formally reasoning about programs and enable powerful introspection techniques — e.g., <a href="https://en.wikipedia.org/wiki/Time_travel_debugging">time travel debugging</a>. However, it doesn’t scale well with complexity, and expressing real-world software requirements as an abstract machine seems like a nightmare. I might be wrong. Maybe they will be the way to write programs in the future. With the advent of AI, who can tell?</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>reading
  |
  | -&gt; readFile
  | &lt;- "content"
  |
writing
  |
  | -&gt; (writeFile, writeFile)
  | &lt;- [undefined, undefined]
  |
final
</code></pre></div></div>

<p>It’s worth noting that there are actually two nested abstract machines at play here. What we discussed above corresponds to the external machine that describes the transition of the program between effects. But these outer transitions are themselves composed of many inner transitions dependent on the programming language at hand. A good formalism to express these inner transitions is the <a href="https://matt.might.net/articles/cesk-machines/">CESK machine</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>reading
  |
  | -&gt; readFile
  | &lt;- "content"
  |
  writeFileEffect()
  |
  writeFileEffect()
  |
  concurrentEffect()
  |
writing
</code></pre></div></div>

<h2 id="supporting-input-actions-with-callbacks">Supporting Input Actions with Callbacks</h2>

<p>Another, more practical way to support input actions is to allow pure functions inside effects. Let’s tweak our effect system to handle these callbacks and use some of the big-brain Haskell names.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Runtime [impure] //</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">readFile</span><span class="p">,</span> <span class="nx">writeFile</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:fs/promises</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">run</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">effect</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="nx">writeFile</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">encoding</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">readFile</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="nx">readFile</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">path</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">encoding</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">fmap</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">mapping</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">child</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">liftA2</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">combine</span><span class="p">(...</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">([</span><span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">left</span><span class="p">),</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">right</span><span class="p">)]));</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">bind</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">makeSecond</span><span class="p">(</span><span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">first</span><span class="p">)));</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">return</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">await</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">makeSecond</span><span class="p">(</span><span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">first</span><span class="p">));</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">unknown effect</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// Prelude [pure] //</span>
<span class="kd">const</span> <span class="nx">readFileEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">readFile</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">writeFileEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">writeFile</span><span class="dl">"</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">,</span>
<span class="p">});</span>
<span class="c1">// Effects are functors:</span>
<span class="kd">const</span> <span class="nx">fmapEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">mapping</span><span class="p">,</span> <span class="nx">child</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">fmap</span><span class="dl">"</span><span class="p">,</span> <span class="nx">mapping</span><span class="p">,</span> <span class="nx">child</span><span class="p">,</span>
<span class="p">});</span>
<span class="c1">// Effects are applicatives:</span>
<span class="kd">const</span> <span class="nx">liftA2Effect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">combine</span><span class="p">,</span> <span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">liftA2</span><span class="dl">"</span><span class="p">,</span> <span class="nx">left</span><span class="p">,</span> <span class="nx">right</span><span class="p">,</span> <span class="nx">combine</span><span class="p">,</span>
<span class="p">});</span>
<span class="c1">// Effects are monads:</span>
<span class="kd">const</span> <span class="nx">bindEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">first</span><span class="p">,</span> <span class="nx">makeSecond</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">bind</span><span class="dl">"</span><span class="p">,</span> <span class="nx">first</span><span class="p">,</span> <span class="nx">makeSecond</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">returnEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">return</span><span class="dl">"</span><span class="p">,</span> <span class="nx">result</span><span class="p">,</span>
<span class="p">});</span>

<span class="c1">// Main [pure] //</span>
<span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="nx">bindEffect</span><span class="p">(</span>
  <span class="nx">readFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">file.txt</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">(</span><span class="nx">content</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">liftA2Effect</span><span class="p">(</span>
    <span class="p">(</span><span class="nx">_write1</span><span class="p">,</span> <span class="nx">_write2</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="kc">null</span><span class="p">,</span>
    <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">copy1.txt</span><span class="dl">"</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
    <span class="nx">writeFileEffect</span><span class="p">(</span><span class="dl">"</span><span class="s2">copy2.txt</span><span class="dl">"</span><span class="p">,</span> <span class="nx">content</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">),</span>
<span class="p">);</span>

<span class="c1">// Runtime [impure] //</span>
<span class="k">await</span> <span class="nx">run</span><span class="p">(</span><span class="nx">main</span><span class="p">);</span>
</code></pre></div></div>

<p>The code is more concise and readable, but states are no longer explicit. Indeed, states have been encoded inside the effects. As a result, the two nested abstract machines are no longer cleanly separated.</p>

<p>Also, by polluting effects with functions, they are no longer pure data and cannot be expressed inside a DSL.</p>

<h2 id="supporting-in-memory-mutations">Supporting In-Memory Mutations</h2>

<p>From a functional point of view, memory mutations are no different from IO actions. They are just faster. Let’s use the same system to support them.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Runtime [impure] //</span>
<span class="kd">const</span> <span class="nx">run</span> <span class="o">=</span> <span class="p">(</span><span class="nx">effect</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">get</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">map</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">key</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">set</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">map</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">key</span><span class="p">,</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">val</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">log</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">bind</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">makeSecond</span><span class="p">(</span><span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">first</span><span class="p">)));</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">unknown effect</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// Prelude [pure] //</span>
<span class="kd">const</span> <span class="nx">getEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">map</span><span class="p">,</span> <span class="nx">key</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">get</span><span class="dl">"</span><span class="p">,</span> <span class="nx">map</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">setEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">map</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">val</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">set</span><span class="dl">"</span><span class="p">,</span> <span class="nx">map</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">val</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">logEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">log</span><span class="dl">"</span><span class="p">,</span> <span class="nx">message</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">bindEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">first</span><span class="p">,</span> <span class="nx">makeSecond</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">bind</span><span class="dl">"</span><span class="p">,</span> <span class="nx">first</span><span class="p">,</span> <span class="nx">makeSecond</span><span class="p">,</span>
<span class="p">});</span>

<span class="c1">// Main [pure] //</span>
<span class="kd">let</span> <span class="nx">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="nx">bindEffect</span><span class="p">(</span>
  <span class="nx">setEffect</span><span class="p">(</span><span class="nx">map</span><span class="p">,</span> <span class="dl">"</span><span class="s2">foo</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">bar</span><span class="dl">"</span><span class="p">),</span>
  <span class="p">(</span><span class="nx">_</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">bindEffect</span><span class="p">(</span>
    <span class="nx">getEffect</span><span class="p">(</span><span class="nx">map</span><span class="p">,</span> <span class="dl">"</span><span class="s2">foo</span><span class="dl">"</span><span class="p">),</span>
    <span class="nx">logEffect</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">);</span>

<span class="c1">// Runtime [impure] //</span>
<span class="nx">run</span><span class="p">(</span><span class="nx">main</span><span class="p">);</span> <span class="c1">// logs bar</span>
</code></pre></div></div>

<h2 id="supporting-timeouts">Supporting Timeouts</h2>

<p>Up until now, effects had a clear before and after, which only made it necessary to add callbacks in the <code class="language-plaintext highlighter-rouge">bind</code> effect combinator. This is no longer the case with timers, which are indeed effects as they depend on the global state of the JavaScript VM. For instance, <code class="language-plaintext highlighter-rouge">setTimeout</code> is not referentially transparent because <code class="language-plaintext highlighter-rouge">setTimeout(() =&gt; {}, 1000) !== setTimeout(() =&gt; {}, 1000)</code>.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Runtime [impure] //</span>
<span class="kd">const</span> <span class="nx">run</span> <span class="o">=</span> <span class="p">(</span><span class="nx">effect</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">setTimeout</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">callback</span><span class="p">()),</span> <span class="nx">effect</span><span class="p">.</span><span class="nx">time</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">clearTimeout</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">clearTimeout</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">timer</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">log</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">message</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">bind</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">makeSecond</span><span class="p">(</span><span class="nx">run</span><span class="p">(</span><span class="nx">effect</span><span class="p">.</span><span class="nx">first</span><span class="p">)));</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">unknown effect</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="c1">// Prelude [pure] //</span>
<span class="kd">const</span> <span class="nx">setTimeoutEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">callback</span><span class="p">,</span> <span class="nx">time</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">setTimeout</span><span class="dl">"</span><span class="p">,</span> <span class="nx">callback</span><span class="p">,</span> <span class="nx">time</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">clearTimeoutEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">timer</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">clearTimeout</span><span class="dl">"</span><span class="p">,</span> <span class="nx">timer</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">logEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">log</span><span class="dl">"</span><span class="p">,</span> <span class="nx">message</span><span class="p">,</span>
<span class="p">});</span>
<span class="kd">const</span> <span class="nx">bindEffect</span> <span class="o">=</span> <span class="p">(</span><span class="nx">first</span><span class="p">,</span> <span class="nx">makeSecond</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">bind</span><span class="dl">"</span><span class="p">,</span> <span class="nx">first</span><span class="p">,</span> <span class="nx">makeSecond</span><span class="p">,</span>
<span class="p">});</span>

<span class="c1">// Main [pure] //</span>
<span class="kd">const</span> <span class="nx">main</span> <span class="o">=</span> <span class="nx">bindEffect</span><span class="p">(</span>
  <span class="nx">setTimeoutEffect</span><span class="p">(</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">this should not be printed</span><span class="dl">"</span><span class="p">),</span>
    <span class="mi">2000</span><span class="p">,</span>
  <span class="p">),</span>
  <span class="p">(</span><span class="nx">timer</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">setTimeoutEffect</span><span class="p">(</span>
    <span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">clearTimeoutEffect</span><span class="p">(</span><span class="nx">timer</span><span class="p">),</span>
    <span class="mi">1000</span><span class="p">,</span>
  <span class="p">),</span>
<span class="p">);</span>

<span class="c1">// Runtime [impure] //</span>
<span class="nx">run</span><span class="p">(</span><span class="nx">main</span><span class="p">);</span>
</code></pre></div></div>

<p>Many other effects also require a callback directly inside the effect rather than inside the <code class="language-plaintext highlighter-rouge">bind</code> combinator: listening to HTTP requests, listening to user clicks on a button, etc. Note that callbacks inside the <code class="language-plaintext highlighter-rouge">bind</code> combinator can be nicely chained whereas callbacks directly inside the effects require manual nesting to be composed. That is the reason why you should prefer promises wherever they make sense despite what Mikeal Rogers says <a href="https://youtu.be/GaqxIMLLOu8?t=380">here</a>.</p>

<h2 id="takeaway">Takeaway</h2>

<p>If you have the opportunity to work with a pure functional language, great. But let’s be real, most of us are stuck with imperative languages. Nonetheless, I found out that reasoning in terms of functional core and imperative shell was helpful. And, rejecting the effects to the border of the program is generally a good idea. I sometimes use effect reification as a design pattern to achieve this. Even if this is not a full-blown generic effect system, it is already beneficial.</p>

<p>I had to instrument JavaScript files for my <a href="http://github.com/getappmap/appmap-agent-js">work</a>. This required fetching the associated source map file and source files. I ended up rejecting <code class="language-plaintext highlighter-rouge">readFile</code> effects by reifying them into URL requests:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">readFile</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:fs/promises</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// https://github.com/getappmap/appmap-agent-js/blob/055d138abb9dba260db8fc95ad412dcf339be3e4/components/instrumentation/default/codebase.mjs#L21</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">extractMissingUrlArrayPure</span> <span class="o">=</span> <span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">files</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// may return:</span>
  <span class="c1">// - the file url to be instrumented</span>
  <span class="c1">// - the url of the source map file</span>
  <span class="c1">// - the url of the source files</span>
<span class="p">};</span>

<span class="c1">// https://github.com/getappmap/appmap-agent-js/blob/055d138abb9dba260db8fc95ad412dcf339be3e4/components/instrumentation/default/index.mjs#L21</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">instrumentPure</span> <span class="o">=</span> <span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">files</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// return: the instrumented content of the file.</span>
<span class="p">};</span>

<span class="c1">// https://github.com/getappmap/appmap-agent-js/blob/055d138abb9dba260db8fc95ad412dcf339be3e4/components/agent/default/index.mjs#L77</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">instrumentImpure</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">content</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">files</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">([</span><span class="nx">url</span><span class="p">,</span> <span class="nx">content</span><span class="p">]);</span>
  <span class="k">while</span> <span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">urls</span> <span class="o">=</span> <span class="nx">extractMissingUrlArrayPure</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">file</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">urls</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nx">instrumentPure</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">file</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">url</span> <span class="k">of</span> <span class="nx">urls</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">files</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="k">await</span> <span class="nx">readFile</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="dl">"</span><span class="s2">utf8</span><span class="dl">"</span><span class="p">));</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>]]></content><author><name>Laurent Christophe</name></author><summary type="html"><![CDATA[This is my attempt at demystifying effect systems of pure functional languages and how to implement them in impure imperative languages.]]></summary></entry><entry><title type="html">Mutations make me paranoid</title><link href="https://lachrist.github.io/blog/2023/04/27/mutation/" rel="alternate" type="text/html" title="Mutations make me paranoid" /><published>2023-04-27T00:00:00+00:00</published><updated>2023-04-27T00:00:00+00:00</updated><id>https://lachrist.github.io/blog/2023/04/27/mutation</id><content type="html" xml:base="https://lachrist.github.io/blog/2023/04/27/mutation/"><![CDATA[<p>The sad, sad reality of JavaScript and most programming languages out there is that there is little to no guarantee on what a function can do.</p>

<h2 id="little-space-guarantee">Little Space Guarantee</h2>

<p>The amount of value that a function can reach and mutate is enormous. That is because the object graph is super connected. Think of the famous “gorilla and banana” problem.</p>

<blockquote>
  <p>I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. – Joe Armstrong</p>
</blockquote>

<p>I would distinguish between two flavors of mutation. First, the <em>bad</em>: mutation of arguments. But at least it does not break referential transparency. And the caller can still check out the argument after calling the function to see what’s up. I’m guilty of using those and even getting comfortable doing so. It is neat for implementing state transition functions without having to reconstruct an entire new state. I mean by state transition functions, functions of type: <code class="language-plaintext highlighter-rouge">(state, input) -&gt; (state, output)</code>. That is pretty much any method you would find on objects in OOP.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// state transition function:</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">createGen</span> <span class="o">=</span> <span class="p">(</span><span class="nx">seed</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">({</span> <span class="nx">seed</span> <span class="p">});</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">random</span> <span class="o">=</span> <span class="p">(</span><span class="nx">gen</span><span class="p">,</span> <span class="nx">min</span><span class="p">,</span> <span class="nx">max</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">gen</span><span class="p">.</span><span class="nx">seed</span> <span class="o">=</span> <span class="nx">computeNext</span><span class="p">(</span><span class="nx">gen</span><span class="p">.</span><span class="nx">seed</span><span class="p">);</span>
  <span class="k">return</span> <span class="nx">randomFromSeed</span><span class="p">(</span><span class="nx">gen</span><span class="p">.</span><span class="nx">seed</span><span class="p">,</span> <span class="nx">min</span><span class="p">,</span> <span class="nx">max</span><span class="p">);</span>
<span class="p">};</span>

<span class="c1">// With OOP syntactic sugar:</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">Gen</span> <span class="p">{</span>
  <span class="kd">constructor</span> <span class="p">(</span><span class="nx">seed</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">seed</span> <span class="o">=</span> <span class="nx">seed</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="nx">random</span> <span class="p">(</span><span class="nx">min</span><span class="p">,</span> <span class="nx">max</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">seed</span> <span class="o">=</span> <span class="nx">computeNext</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">seed</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">randomFromSeed</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">seed</span><span class="p">,</span> <span class="nx">min</span><span class="p">,</span> <span class="nx">max</span><span class="p">);</span> 
  <span class="p">}</span> 
<span class="p">};</span>
</code></pre></div></div>

<p>Second, the <em>ugly</em>: mutation of values in free variables. This is worse because it introduces implicit state and breaks referential transparency. The caller has no idea what is going on. The worst you can do is mutating values reached by global variables. People will tell you to never do that. But I don’t care; sometimes you have to do it. I do dynamic program analysis for a living, and sometimes I have to do this abomination even if it always bites me in the ass. This kind of code is hard to maintain and often requires diving deep into it to understand the link between seemingly unrelated parts.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Original version:</span>
<span class="kd">const</span> <span class="nx">square</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">x</span><span class="p">;</span>
<span class="c1">// Instrumented version:</span>
<span class="kd">const</span> <span class="nx">squareInstrumented</span> <span class="o">=</span> <span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">LOG</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">"</span><span class="s2">begin-square</span><span class="dl">"</span><span class="p">);</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="k">return</span> <span class="nx">x</span> <span class="o">*</span> <span class="nx">x</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
    <span class="nx">LOG</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">"</span><span class="s2">end-square</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span> 
</code></pre></div></div>

<h2 id="no-time-guarantee">No Time Guarantee</h2>

<p>Bloody hell, mutations can even happen after the function returns. Now the caller is getting real paranoid. It is not sufficient to check whatever mess the function did right after it returned. But it could mess things later! I struggled with this recently. I needed to record some events and flush them. I ended up doing something like this:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// hook.mjs</span>
<span class="k">import</span> <span class="nx">process</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:process</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createHook</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">node:async_hooks</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">hook</span> <span class="o">=</span> <span class="p">(</span><span class="nx">buffer</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">process</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">uncaughtErrorMonitor</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">buffer</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
      <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">error</span><span class="dl">"</span><span class="p">,</span>
      <span class="nx">error</span><span class="p">,</span>
    <span class="p">});</span>
  <span class="p">});</span>
  <span class="nx">createHook</span><span class="p">({</span>
    <span class="na">before</span><span class="p">:</span> <span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">buffer</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
        <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">before</span><span class="dl">"</span><span class="p">,</span>
        <span class="nx">id</span><span class="p">,</span>
      <span class="p">});</span>
    <span class="p">},</span>
    <span class="na">after</span><span class="p">:</span> <span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nx">buffer</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span>
        <span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">after</span><span class="dl">"</span><span class="p">,</span>
        <span class="nx">id</span><span class="p">,</span>
      <span class="p">});</span>
    <span class="p">},</span>
  <span class="p">}).</span><span class="nx">enable</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// flush.mjs</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Socket</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">node:net</span><span class="dl">"</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">flush</span> <span class="o">=</span> <span class="p">(</span><span class="nx">buffer</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">socket</span> <span class="o">=</span> <span class="nx">connect</span><span class="p">(</span><span class="dl">"</span><span class="s2">localhost:8080</span><span class="dl">"</span><span class="p">);</span>
  <span class="nx">setInterval</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">socket</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">buffer</span><span class="p">));</span>
    <span class="nx">buffer</span><span class="p">.</span><span class="nx">length</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="p">},</span> <span class="mi">1000</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div></div>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.mjs</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">hook</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./hook.mjs</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">flush</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./flush.mjs</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="p">[];</span>
<span class="nx">hook</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="nx">flush</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
</code></pre></div></div>

<p>I think this is bad code because the interaction between <code class="language-plaintext highlighter-rouge">hook.mjs</code> and <code class="language-plaintext highlighter-rouge">flush.mjs</code> is not immediately apparent from <code class="language-plaintext highlighter-rouge">main.mjs</code>. Adapting <code class="language-plaintext highlighter-rouge">hook.mjs</code> and <code class="language-plaintext highlighter-rouge">flush.mjs</code> to use callbacks can make this interaction explicit.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.mjs</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">hook</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./hook.mjs</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">flush</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./flush.mjs</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="p">[];</span>
<span class="nx">hook</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">buffer</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">event</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">flush</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">buffer</span><span class="p">.</span><span class="nx">splice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">buffer</span><span class="p">.</span><span class="nx">length</span><span class="p">));</span>
</code></pre></div></div>

<p>Maybe we can write saner JavaScript code by following these two rules:</p>
<ul>
  <li>Arguments can only be mutated synchronously.</li>
  <li>Asynchronous mutations can only happen with explicit callbacks.</li>
</ul>]]></content><author><name>Laurent Christophe</name></author><summary type="html"><![CDATA[The sad, sad reality of JavaScript and most programming languages out there is that there is little to no guarantee on what a function can do.]]></summary></entry></feed>