<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matheus de Camargo Marques</title>
    <description>The latest articles on DEV Community by Matheus de Camargo Marques (@matheuscamarques).</description>
    <link>https://dev.to/matheuscamarques</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F882243%2Fde69153a-a393-4f28-9761-e5b60402c45b.jpeg</url>
      <title>DEV Community: Matheus de Camargo Marques</title>
      <link>https://dev.to/matheuscamarques</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/matheuscamarques"/>
    <language>en</language>
    <item>
      <title>A SEDA Web Server in Erlang: When an Architecture Paper Becomes a Teaching Exercise</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Thu, 05 Nov 2026 12:16:17 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/a-seda-web-server-in-erlang-when-an-architecture-paper-becomes-a-teaching-exercise-4klg</link>
      <guid>https://dev.to/matheuscamarques/a-seda-web-server-in-erlang-when-an-architecture-paper-becomes-a-teaching-exercise-4klg</guid>
      <description>&lt;h1&gt;
  
  
  A SEDA Web Server in Erlang: When an Architecture Paper Becomes a Teaching Exercise
&lt;/h1&gt;

&lt;p&gt;Traditional web servers based on synchronous TCP connections hit a wall at scale. Each connection consumes an OS thread, each thread reserves megabytes of stack, and the context-switching overhead grows until the server spends more time managing threads than serving requests. The usual escape hatch — distributing across clusters — adds cost and operational complexity without solving the underlying problem.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;ERLDA&lt;/strong&gt; paper by Fernando Areias and Adolfo Neto, presented at the I Workshop em Engenharia de Software para Programação Funcional (SE4FP 2025), argues that Erlang’s BEAM virtual machine and the &lt;strong&gt;SEDA (Staged Event-Driven Architecture)&lt;/strong&gt; model are a natural fit for this problem. But more interesting than the argument is the &lt;em&gt;exercise&lt;/em&gt; it suggests: implement a minimal HTTP server with three SEDA stages under OTP supervision.&lt;/p&gt;

&lt;p&gt;That exercise is a masterclass in architectural thinking. It forces the student to confront the same design decisions that production systems face — queue backpressure, stage isolation, failure recovery — in a codebase small enough to reason about completely. This article walks through that exercise, explains why Erlang maps to SEDA so cleanly, and connects it to the broader arc of learning to build verifiable concurrent systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  What SEDA actually proposes
&lt;/h2&gt;

&lt;p&gt;SEDA was introduced by Welsh, Culler, and Brewer in 2001 as a response to the limitations of both thread-per-request and pure event-driven servers. Its core idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Decompose the application into stages connected by explicit event queues. Each stage performs a subset of request processing and is executed by a pool of threads.&lt;/strong&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A stage has three components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;incoming event queue&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;thread pool&lt;/strong&gt; that drives execution.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;application-supplied event handler&lt;/strong&gt; that processes events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The queues are not an implementation detail. They are the &lt;strong&gt;control boundary&lt;/strong&gt; that lets each stage be managed independently. If the processor stage becomes a bottleneck, its queue grows, and the system can shed load, throttle, or scale that stage alone — without touching the acceptor or the writer.&lt;/p&gt;

&lt;p&gt;SEDA was designed for Java, but its abstractions are language-agnostic. The question the ERLDA paper asks is: what happens when you implement SEDA in a language where &lt;strong&gt;processes are cheap, mailboxes are queues, and supervision is a language primitive&lt;/strong&gt;?&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Erlang is the natural substrate for SEDA
&lt;/h2&gt;

&lt;p&gt;The mapping between SEDA and Erlang/OTP is almost embarrassingly direct:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SEDA concept&lt;/th&gt;
&lt;th&gt;Erlang equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stage&lt;/td&gt;
&lt;td&gt;A process (or pool of processes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event queue&lt;/td&gt;
&lt;td&gt;A process mailbox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread pool&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;poolboy&lt;/code&gt; pool or a supervisor-managed set of workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event handler&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;gen_server&lt;/code&gt; callback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queue backpressure&lt;/td&gt;
&lt;td&gt;Selective receive, bounded mailboxes, or explicit queue processes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stage isolation&lt;/td&gt;
&lt;td&gt;Process isolation (separate heaps, separate failure domains)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stage recovery&lt;/td&gt;
&lt;td&gt;OTP supervisor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is not a coincidence. Erlang’s actor model &lt;em&gt;is&lt;/em&gt; a staged architecture by default: each process has an isolated mailbox, processes communicate by asynchronous message passing, and failures are contained to the process that crashes. SEDA formalizes what Erlang already does natively.&lt;/p&gt;

&lt;p&gt;The ERLDA paper makes the memory argument explicit: the BEAM VM is designed to support &lt;strong&gt;millions of simultaneous processes with efficient resource usage&lt;/strong&gt;, prioritizing vertical scalability and operational simplicity over cluster-based horizontal scaling. A SEDA stage implemented as a pool of Erlang processes costs kilobytes per process, not megabytes per thread. The architectural model and the runtime substrate are aligned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three-stage exercise
&lt;/h2&gt;

&lt;p&gt;The exercise proposed in the ERLDA discussion — and the one I want to walk through here — is a minimal HTTP server with three stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Acceptor&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Processor&lt;/span&gt; &lt;span class="nv"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Response&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Writer&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each arrow is a message-passing boundary. Each stage is a separate process or pool. OTP supervision wraps the whole thing so that the failure of any stage does not bring down the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: The acceptor
&lt;/h3&gt;

&lt;p&gt;The acceptor owns the listening socket. It accepts connections, reads the raw request bytes, and enqueues them for processing. It does not parse, does not validate, does not generate responses. Its only job is to move bytes from the network into the pipeline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;acceptor&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ListenSocket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reuseaddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}]),&lt;/span&gt;
    &lt;span class="nf"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;ListenSocket&lt;/span&gt;&lt;span class="p"&gt;}}.&lt;/span&gt;

&lt;span class="nf"&gt;handle_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;LS&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;LS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
            &lt;span class="nf"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The acceptor is a &lt;code&gt;gen_server&lt;/code&gt; with a single job. If it crashes, the supervisor restarts it. The listening socket is recreated, and the server continues. &lt;strong&gt;No other stage is affected.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: The request queue and processor pool
&lt;/h3&gt;

&lt;p&gt;The request queue is a process that buffers incoming requests and distributes them to a pool of processor workers. In a minimal implementation, the queue itself can be a &lt;code&gt;gen_server&lt;/code&gt; that holds a bounded list and dispatches to available workers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pick_worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processor worker parses the HTTP request, generates a response (for the exercise, a simple JSON payload), and enqueues the result for the writer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;

&lt;span class="nf"&gt;generate_response&lt;/span&gt;&lt;span class="p"&gt;(_&lt;/span&gt;&lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s"&gt;"HTTP/1.1 200 OK&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Type: application/json&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s"&gt;Content-Length: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;integer_to_binary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;byte_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processor pool is where the actual work happens. In a real system, this is where you would add CPU-bound processing, database queries, or calls to downstream services. In the exercise, it is deliberately trivial so that the &lt;em&gt;architecture&lt;/em&gt; — not the business logic — is the focus.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: The response queue and writer
&lt;/h3&gt;

&lt;p&gt;The writer consumes responses from the response queue and sends them back to the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, one job. One process. One failure domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supervision: the glue that makes it resilient
&lt;/h3&gt;

&lt;p&gt;The entire pipeline is wrapped in an OTP supervision tree. The exact strategy depends on the desired semantics, but a &lt;code&gt;one_for_one&lt;/code&gt; supervisor over the three stages is sufficient for the exercise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;SupFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;Children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;acceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;acceptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}},&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;processor_sup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;processor_sup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}},&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}},&lt;/span&gt;
        &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]}}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;SupFlags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Children&lt;/span&gt;&lt;span class="p"&gt;}}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the payoff of the exercise. In Java, implementing SEDA requires a thread pool per stage, a bounded queue per stage, a rejection policy per queue, and a restart mechanism per stage — all wired together manually. In Erlang, the thread pool is a supervisor, the queue is a mailbox, and the restart mechanism is a supervisor callback. &lt;strong&gt;The architecture is the language.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What the exercise actually teaches
&lt;/h2&gt;

&lt;p&gt;The three-stage server is small enough to implement in an afternoon, but it surfaces every decision that matters in a concurrent system:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Backpressure is explicit
&lt;/h3&gt;

&lt;p&gt;If the processor pool cannot keep up with the acceptor, the request queue grows. In a real system, you bound the queue and define a rejection policy — &lt;code&gt;{error, overload}&lt;/code&gt; or a 503 response. In the exercise, the student sees the queue as a &lt;strong&gt;first-class object&lt;/strong&gt; that can grow, shrink, and overflow. The architecture forces the question: &lt;em&gt;what happens when stage N is slower than stage N−1?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Failure isolation is structural
&lt;/h3&gt;

&lt;p&gt;If the processor crashes on a malformed request, only that processor process dies. The acceptor continues accepting. The writer continues writing. The supervisor restarts the processor. The student sees that &lt;strong&gt;failure is contained by the topology&lt;/strong&gt;, not by try/catch blocks scattered through the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Stages can be scaled independently
&lt;/h3&gt;

&lt;p&gt;In the exercise, the processor pool is a supervisor with N children. To handle more load, increase N. The acceptor and writer remain unchanged. This is the SEDA promise made concrete: &lt;strong&gt;each stage is an independent scaling unit&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The queue is the control boundary
&lt;/h3&gt;

&lt;p&gt;Because the queues are explicit, the student can &lt;em&gt;see&lt;/em&gt; where load accumulates. In a thread-per-request server, there is no queue — there are just threads blocking on I/O. In SEDA, the queue is a named, observable, controllable thing. The architecture makes the system &lt;strong&gt;debuggable in a way that thread-per-request systems are not&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  From exercise to verification: the next step
&lt;/h2&gt;

&lt;p&gt;The three-stage server is a pedagogical milestone, but it is not the end of the learning arc. Once the student has built a SEDA system in Erlang, the natural next question is: &lt;em&gt;how do I know this is correct?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is where the verification literature connects. The same stage-and-queue topology that makes the system scalable also makes it &lt;strong&gt;verifiable&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ACTORCHESTRA&lt;/strong&gt; can express cross-stage causal properties — “whenever the acceptor enqueues a request, the writer eventually sends a response” — and monitor them at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDMon&lt;/strong&gt; can detect deadlocks that emerge from synchronous calls between stages, proving soundness and completeness from the outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NVLang&lt;/strong&gt; can enforce protocol conformance at compile time, ensuring that the processor never sends a message the writer cannot handle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SEDA exercise is the &lt;strong&gt;construction&lt;/strong&gt; phase. Verification is the &lt;strong&gt;assurance&lt;/strong&gt; phase. Staroletov’s pedagogical arc — refactoring, recursion, processes, supervision, verification — culminates in exactly this: a system that is not only built but &lt;em&gt;provably correct&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: architecture as a teaching tool
&lt;/h2&gt;

&lt;p&gt;The ERLDA paper’s contribution is not a novel web server. It is a demonstration that &lt;strong&gt;SEDA and Erlang/OTP are conceptually isomorphic&lt;/strong&gt; — and that this isomorphism can be taught through a small, complete, buildable exercise.&lt;/p&gt;

&lt;p&gt;The three-stage HTTP server forces the student to think in terms of queues, stages, failure domains, and supervision. It makes backpressure visible, isolation structural, and scaling explicit. And when the student finishes, they have not just written a server — they have internalized an architecture that applies to every concurrent system they will build afterward.&lt;/p&gt;

&lt;p&gt;That is what the best teaching exercises do: they are small enough to finish, and large enough to contain the whole problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ERLDA:&lt;/strong&gt; Areias, Fernando; Neto, Adolfo. &lt;em&gt;“ERLDA: Explorando Concorrência e Resiliência com SEDA em Erlang.”&lt;/em&gt; In: I Workshop em Engenharia de Software para Programação Funcional (SE4FP 2025), Recife/PE, pp. 27–32. DOI: &lt;a href="https://doi.org/10.5753/se4fp.2025.14570" rel="noopener noreferrer"&gt;10.5753/se4fp.2025.14570&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEDA (original):&lt;/strong&gt; Welsh, Matt; Culler, David; Brewer, Eric. &lt;em&gt;“SEDA: An Architecture for Well-Conditioned, Scalable Internet Services.”&lt;/em&gt; ACM SIGOPS Operating Systems Review, Vol. 35, No. 5, 2001.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pedagogical context:&lt;/strong&gt; Staroletov, Sergey. &lt;em&gt;“Modern Erlang Teaching from Code Refactoring to Actor System Verification.”&lt;/em&gt; Erlang Workshop 2026. DOI: &lt;a href="https://doi.org/10.1145/3830434.3830946" rel="noopener noreferrer"&gt;10.1145/3830434.3830946&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/fernandoareias/web-server-erl" rel="noopener noreferrer"&gt;github.com/fernandoareias/web-server-erl&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Have you built a SEDA-style system in Erlang — or tried to port the architecture to another runtime? What was the hardest part to get right? Let me know in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>erlang</category>
    </item>
    <item>
      <title>Verifying Actor Systems: Beyond Testing</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Thu, 05 Nov 2026 12:14:08 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/verifying-actor-systems-beyond-testing-f15</link>
      <guid>https://dev.to/matheuscamarques/verifying-actor-systems-beyond-testing-f15</guid>
      <description>&lt;h1&gt;
  
  
  Verifying Actor Systems: Beyond Testing
&lt;/h1&gt;

&lt;p&gt;Actor systems are notoriously difficult to verify through traditional testing. Message ordering is non-deterministic, properties often span multiple actors, and a protocol violation that only manifests under a specific interleaving can hide in production for months.&lt;/p&gt;

&lt;p&gt;The 2025–2026 literature offers three complementary answers to this problem, each attacking a different layer of the verification stack: &lt;strong&gt;runtime verification&lt;/strong&gt; of causal properties, &lt;strong&gt;formal deadlock detection&lt;/strong&gt; from the outside, and &lt;strong&gt;static protocol typing&lt;/strong&gt; at compile time. Together, they sketch a path from “we tested it and it seemed fine” to “we have evidence that it is correct.”&lt;/p&gt;

&lt;p&gt;This article breaks down all three — and connects them back to the pedagogical argument that Erlang’s supervision model is not a set of APIs to memorize, but a set of contracts to understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why testing actor systems is not enough
&lt;/h2&gt;

&lt;p&gt;A unit test checks one process in isolation. An integration test checks a few processes under a &lt;em&gt;specific&lt;/em&gt; schedule. But an actor system’s behaviour depends on &lt;strong&gt;which messages arrive in which order&lt;/strong&gt; — and the number of possible interleavings grows combinatorially with the number of actors.&lt;/p&gt;

&lt;p&gt;Worse, the properties we actually care about are often &lt;strong&gt;cross-actor&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Whenever the order actor confirms a purchase, the inventory actor must have received a reservation first.”&lt;/li&gt;
&lt;li&gt;“If a service sends a request, it eventually receives a response — or is provably deadlocked.”&lt;/li&gt;
&lt;li&gt;“No process ever sends a message that violates its declared protocol.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these can be verified by testing a single process. They require either &lt;strong&gt;observing the running system&lt;/strong&gt; or &lt;strong&gt;proving properties before it runs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The three approaches below take one of those two paths.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Runtime verification: ACTORCHESTRA and WALTZ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ACTORCHESTRA&lt;/strong&gt; is a runtime verification framework for Erlang that automatically tracks &lt;strong&gt;causality across multi-actor interactions&lt;/strong&gt;. It was presented at the 2026 IEEE International Conference on Software Testing, Verification and Validation (ICST) in the paper &lt;em&gt;“In Perfect Harmony: Orchestrating Causality in Actor-Based Systems.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The core problem it solves is stated precisely in the abstract: &lt;em&gt;“Properties frequently span multiple actors with complex causal dependencies, while non-deterministic message interleavings can obscure execution semantics. Moreover, most existing monitoring tools are designed for single-process behavior.”&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;ACTORCHESTRA addresses this in two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Targeted code injection&lt;/strong&gt; into Erlang systems that follow OTP guidelines — establishing the orchestration infrastructure needed to track causal relationships &lt;em&gt;without manual modification of the target system&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WALTZ&lt;/strong&gt;, a specification language that compiles multi-actor properties into executable Erlang monitors that integrate with the instrumented system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A concrete property you can express in WALTZ:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Whenever the order actor sends a confirmation message to the customer actor, the inventory actor must have received a reservation message beforehand.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a &lt;strong&gt;causal chain&lt;/strong&gt; — not a simple state predicate. ACTORCHESTRA monitors the chain at runtime and alerts when the property is violated. Three case studies demonstrate its effectiveness in detecting complex behavioural violations in real-world actor systems.&lt;/p&gt;

&lt;p&gt;The trade-off is explicit: the paper quantifies the &lt;strong&gt;runtime overhead&lt;/strong&gt; of the monitoring infrastructure and analyses the balance between added safety guarantees and execution costs. Runtime verification is not free — but it catches violations that no unit test would ever see.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Formal deadlock detection: DDMon
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DDMon&lt;/strong&gt; (2025, OOPSLA) takes a different tack: instead of instrumenting the system’s source code, it deploys &lt;strong&gt;distributed black-box monitors&lt;/strong&gt; alongside each service and detects deadlocks by &lt;em&gt;only observing incoming and outgoing messages&lt;/em&gt;, exchanging probes with other monitors.&lt;/p&gt;

&lt;p&gt;The formal contribution is significant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A formal model that captures &lt;strong&gt;RPC-based application styles&lt;/strong&gt; — including &lt;code&gt;gen_server&lt;/code&gt; processes in Erlang/OTP.&lt;/li&gt;
&lt;li&gt;A distributed black-box monitoring algorithm proven &lt;strong&gt;sound&lt;/strong&gt; (no false positives) and &lt;strong&gt;complete&lt;/strong&gt; (no false negatives).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Coq mechanisation&lt;/strong&gt; of the results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the first work that formalises, proves the correctness of, and implements distributed black-box monitors for deadlock detection.&lt;/p&gt;

&lt;p&gt;The DDMon tool implements these monitors for Erlang and Elixir applications based on &lt;code&gt;gen_server&lt;/code&gt;, with minimal user intervention. It also optionally collects logs via the Erlang/OTP trace mechanism and visualises them to help diagnose deadlocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why black-box matters:&lt;/strong&gt; the monitoring does not depend on the internal syntax of the services — the process internals are not observable by the monitors. You can attach DDMon to a running system without modifying it, and trust its verdict because the algorithm was proven correct.&lt;/p&gt;

&lt;p&gt;For practitioners, the message is direct: if you have &lt;code&gt;gen_server&lt;/code&gt; processes making chained synchronous calls — a common microservices pattern — DDMon can tell you when they are deadlocked, and its answer is mathematically guaranteed.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Static protocol typing: NVLang
&lt;/h2&gt;

&lt;p&gt;Both ACTORCHESTRA and DDMon operate on a &lt;strong&gt;running system&lt;/strong&gt;. NVLang moves the verification to &lt;strong&gt;compile time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Erlang has no static guarantees about message protocols. Processes communicate by sending arbitrary messages that are filtered by pattern matching at runtime — which means protocol violations are deferred to production failures.&lt;/p&gt;

&lt;p&gt;NVLang is a statically typed functional language that &lt;strong&gt;compiles to Core Erlang&lt;/strong&gt;, preserving interoperability with the existing ecosystem. Its central contribution is that &lt;strong&gt;algebraic data types (ADTs) naturally encode actor protocols&lt;/strong&gt;: each actor declares the sum type representing its message vocabulary, and the type system enforces protocol conformance at compile time.&lt;/p&gt;

&lt;p&gt;Two further constructs complete the picture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typed process identifiers&lt;/strong&gt; (&lt;code&gt;Pid[T]&lt;/code&gt;) encode the protocol an actor expects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Typed futures&lt;/strong&gt; (&lt;code&gt;Future[T]&lt;/code&gt;) provide type-safe request-reply patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By extending Hindley–Milner type inference to track message protocols, NVLang eliminates an entire class of message-passing errors while keeping syntax clean. The type system is formalised with proof sketches for type soundness, demonstrating that &lt;strong&gt;well-typed NVLang programs cannot send messages that violate actor protocols&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;NVLang is a separate language, but its concepts discipline pure Erlang design. Before implementing a &lt;code&gt;gen_server&lt;/code&gt;, define the &lt;strong&gt;sum type of messages it accepts&lt;/strong&gt;. That single act of specification reduces the class of errors that would otherwise only manifest in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the three fit together
&lt;/h2&gt;

&lt;p&gt;The three approaches are not competitors. They occupy different points in the verification timeline:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;When it runs&lt;/th&gt;
&lt;th&gt;What it catches&lt;/th&gt;
&lt;th&gt;What it costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVLang&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compile time&lt;/td&gt;
&lt;td&gt;Protocol violations, malformed messages&lt;/td&gt;
&lt;td&gt;Requires writing in a new language (or disciplining design)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ACTORCHESTRA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Cross-actor causal violations&lt;/td&gt;
&lt;td&gt;Monitoring overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DDMon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;Distributed deadlocks&lt;/td&gt;
&lt;td&gt;Probe exchange overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A mature verification strategy uses all three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before deployment&lt;/strong&gt;, use static typing (or its design discipline) to eliminate protocol-level bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At runtime&lt;/strong&gt;, use ACTORCHESTRA to monitor causal properties that no type can express.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuously&lt;/strong&gt;, use DDMon to detect deadlocks that emerge from the interaction of otherwise-correct services.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The academic connection: verification as the end of a pedagogical arc
&lt;/h2&gt;

&lt;p&gt;This literature connects directly to Staroletov’s 2026 Erlang Workshop paper, &lt;em&gt;“Modern Erlang Teaching from Code Refactoring to Actor System Verification.”&lt;/em&gt; The course it describes moves students from refactoring imperative code, through Taylor-series recursion, to lightweight processes, and finally to &lt;strong&gt;verification of actor systems with Promela/SPIN&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The progression matters. Verification is not taught as an isolated topic — it is the &lt;strong&gt;culmination&lt;/strong&gt; of learning to reason about concurrent behaviour. Students who have refactored away imperative sequencing, internalised pattern matching as type-driven dispatch, and built systems with supervision trees are &lt;strong&gt;ready&lt;/strong&gt; to ask: &lt;em&gt;“How do I know this is correct?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ACTORCHESTRA, DDMon, and NVLang are three answers to that question. Each makes a different trade-off between when verification happens and what it guarantees. Staroletov’s pedagogical point — that Erlang’s model is a set of &lt;strong&gt;contracts to understand&lt;/strong&gt;, not APIs to memorize — applies equally to verification: the goal is not to run a tool, but to know &lt;strong&gt;what property you are checking and why&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: from “it seems fine” to “we have evidence”
&lt;/h2&gt;

&lt;p&gt;Actor systems are too concurrent and too non-deterministic to verify by testing alone. The 2025–2026 literature offers a layered answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NVLang&lt;/strong&gt; proves protocol correctness at compile time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACTORCHESTRA&lt;/strong&gt; monitors causal properties across actors at runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDMon&lt;/strong&gt; proves deadlock freedom from the outside, with soundness and completeness guarantees.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, they move the field from &lt;em&gt;“we tested it and it seemed fine”&lt;/em&gt; to &lt;em&gt;“we have evidence that it is correct.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For anyone building or teaching actor systems, the lesson is clear: verification is not a luxury bolted on after the system works. It is the &lt;strong&gt;final stage of learning to reason about concurrency&lt;/strong&gt; — and the tools now exist to make it practical.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ACTORCHESTRA / WALTZ:&lt;/strong&gt; &lt;em&gt;“In Perfect Harmony: Orchestrating Causality in Actor-Based Systems.”&lt;/em&gt; IEEE ICST 2026. &lt;a href="https://ieeexplore.ieee.org/document/11599956" rel="noopener noreferrer"&gt;IEEE Xplore&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DDMon:&lt;/strong&gt; Rowicki, Francalanza, Scalas. &lt;em&gt;“Correct Black-Box Monitors for Distributed Deadlock Detection: Formalisation and Implementation.”&lt;/em&gt; PACMPL (OOPSLA 2025). &lt;a href="https://doi.org/10.1145/3763069" rel="noopener noreferrer"&gt;DOI: 10.1145/3763069&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NVLang:&lt;/strong&gt; Guerreiro et al. &lt;em&gt;“NVLang: Unified Static Typing for Actor-Based Concurrency on the BEAM.”&lt;/em&gt; arXiv:2512.05224, Dec 2025. &lt;a href="https://arxiv.org/abs/2512.05224" rel="noopener noreferrer"&gt;arXiv&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pedagogical context:&lt;/strong&gt; Staroletov, Sergey. &lt;em&gt;“Modern Erlang Teaching from Code Refactoring to Actor System Verification.”&lt;/em&gt; Erlang Workshop 2026. DOI: &lt;a href="https://doi.org/10.1145/3830434.3830946" rel="noopener noreferrer"&gt;10.1145/3830434.3830946&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Have you used runtime verification, deadlock monitoring, or static typing in a production actor system? What did it actually catch — and what did it miss? Let me know in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>erlang</category>
      <category>elixir</category>
    </item>
    <item>
      <title>OTP Supervision Is Not a Pattern You Can Copy: Lessons from *Supervision Without Blocking* (2026)</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Thu, 05 Nov 2026 12:11:40 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/otp-supervision-is-not-a-pattern-you-can-copy-lessons-from-supervision-without-blocking-2026-1ldg</link>
      <guid>https://dev.to/matheuscamarques/otp-supervision-is-not-a-pattern-you-can-copy-lessons-from-supervision-without-blocking-2026-1ldg</guid>
      <description>&lt;h1&gt;
  
  
  OTP Supervision Is Not a Pattern You Can Copy: Lessons from &lt;em&gt;Supervision Without Blocking&lt;/em&gt; (2026)
&lt;/h1&gt;

&lt;p&gt;Actor frameworks for Rust, the JVM, and .NET all advertise “Erlang/OTP-style supervision.” It has become a headline feature, a selling point, a box to check. But a 2026 paper by Sam Uwejan and Erdogan Dogdu (Angelo State University), &lt;em&gt;Supervision Without Blocking: An Evidence-Driven Redesign of OTP-Style Fault Tolerance for Cooperative Runtimes&lt;/em&gt;, delivers an uncomfortable finding: many of those implementations inherit OTP’s semantics &lt;strong&gt;without understanding the contracts that make them work&lt;/strong&gt; — and that gap produces real, catalogued defects .&lt;/p&gt;

&lt;p&gt;This article unpacks what the paper found, why OTP’s supervision guarantees are harder to port than they look, and what it means for anyone building fault-tolerant systems outside the BEAM.&lt;/p&gt;




&lt;h2&gt;
  
  
  The audit: 24 defect classes, one root cause
&lt;/h2&gt;

&lt;p&gt;Uwejan and Dogdu did not set out to criticize ports. They set out to &lt;strong&gt;audit every supervision guarantee against OTP’s own documentation and source code&lt;/strong&gt;, read verbatim at pinned versions, and then against the source of three further Rust actor frameworks at fixed revisions . Blind adversarial audits and the cross-framework study surfaced &lt;strong&gt;24 distinct defect classes&lt;/strong&gt; .&lt;/p&gt;

&lt;p&gt;The striking result is not the number. It is the concentration: &lt;strong&gt;eight of the 24 defect classes reduce to a single root cause&lt;/strong&gt; — “reconstructing, by explicit mechanism, the atomicity a synchronous supervisor gets implicitly” .&lt;/p&gt;

&lt;p&gt;In other words: the ports copied the &lt;em&gt;shape&lt;/em&gt; of OTP supervision without copying the &lt;em&gt;substrate&lt;/em&gt; that makes the shape consistent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why OTP supervision works: the blocking supervisor
&lt;/h2&gt;

&lt;p&gt;An OTP supervisor is a single-threaded process. It blocks for the duration of every restart and termination step it performs. Its consistency contracts — the guarantees that make “let it crash” safe — are &lt;strong&gt;silent consequences of that blocking&lt;/strong&gt; .&lt;/p&gt;

&lt;p&gt;Consider what this means concretely. When an OTP supervisor restarts a child:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It receives the exit signal.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;synchronously&lt;/strong&gt; terminates the remaining children according to the shutdown strategy.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;synchronously&lt;/strong&gt; starts the new child.&lt;/li&gt;
&lt;li&gt;It only then returns to its message loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because the supervisor is single-threaded and blocked, &lt;strong&gt;no other lifecycle message can interleave&lt;/strong&gt; with those steps. The atomicity is not enforced by locks or transactions. It is a natural consequence of the supervisor doing one thing at a time and not processing its mailbox until that thing is done.&lt;/p&gt;

&lt;p&gt;This is the contract that ports miss. A cooperative runtime like Tokio has an asynchronous scheduler. To keep the supervisor responsive, frameworks &lt;strong&gt;drop the blocking&lt;/strong&gt; . But once the supervisor no longer blocks, the atomicity disappears — and the port must reconstruct it explicitly, or inherit the absence as “unexamined risk” .&lt;/p&gt;




&lt;h2&gt;
  
  
  The unpaid price: push-based lifecycle signaling
&lt;/h2&gt;

&lt;p&gt;The paper identifies a second pattern across all four examined codebases: they independently adopted &lt;strong&gt;push-based lifecycle signaling&lt;/strong&gt;, “whose losses and reorderings are the unpaid price of an asynchronous supervisor” .&lt;/p&gt;

&lt;p&gt;In a blocking supervisor, lifecycle events are processed in order because the supervisor processes one message at a time. In an asynchronous supervisor, lifecycle signals are pushed to the supervisor while it may be busy doing something else. Signals can be &lt;strong&gt;lost&lt;/strong&gt; under backpressure. They can be &lt;strong&gt;reordered&lt;/strong&gt; relative to ordinary messages. And the supervisor may make decisions based on a lifecycle state that has already changed.&lt;/p&gt;

&lt;p&gt;These are not hypothetical bugs. They are structural consequences of removing the blocking without replacing the atomicity it provided.&lt;/p&gt;




&lt;h2&gt;
  
  
  The redesign: paying for the trade deliberately
&lt;/h2&gt;

&lt;p&gt;Uwejan and Dogdu do not argue that blocking is the only correct design. They argue that the trade must be &lt;strong&gt;priced&lt;/strong&gt;. Their proposed runtime, &lt;strong&gt;ANON&lt;/strong&gt;, closes all 24 catalogued defect classes through three mechanisms :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supervisor-owned completion values.&lt;/strong&gt; Instead of pushing lifecycle signals to the supervisor and hoping they arrive in order, the supervisor holds each child’s completion as a value returned by a task set it owns. Completion is &lt;strong&gt;pulled&lt;/strong&gt;, not pushed — and pulling from an owned set preserves ordering by construction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-incarnation identity.&lt;/strong&gt; Every process instance carries an identity drawn from a monotonic counter. A late signal from a previous incarnation cannot be mistaken for a signal from the current one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A single lifecycle state machine.&lt;/strong&gt; All liveness bookkeeping collapses into one state machine with a single transition function. There are no ad-hoc flags to fall out of sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is validated by &lt;strong&gt;241 tests&lt;/strong&gt;, including regression reproductions that were verified to fail against pre-fix code by temporarily reverting the fix, then re-applied and observed to pass across 10 runs .&lt;/p&gt;




&lt;h2&gt;
  
  
  What ANON adds — and what it cannot
&lt;/h2&gt;

&lt;p&gt;ANON holds OTP’s verbatim-verified contracts while adding two properties OTP does not offer :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A supervisor that remains responsive throughout group restarts.&lt;/strong&gt; Because it never blocks, it can answer status queries and manual commands even while a restart group is in progress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message handlers that are never cancelled mid-execution.&lt;/strong&gt; On the BEAM, an untrappable kill can arrive preemptively. ANON declines to imitate that, because cancelling a handler mid-execution drops local state at an arbitrary await point and exports a cancellation-safety obligation into every user handler .&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But the paper is careful about the &lt;strong&gt;forcing boundary&lt;/strong&gt;. Erlang’s kill is guaranteed by a virtual machine the language owns. An in-process runtime on a host language owns neither the scheduler nor the process. Its strongest primitive is task abort at a yield point. A handler that never yields defeats both the signal and the abort backstop. And an operating-system kill of the host runs no teardown at all .&lt;/p&gt;

&lt;p&gt;This is the honest limit of any OTP-style supervision built outside the BEAM: &lt;strong&gt;some guarantees cannot be reconstructed&lt;/strong&gt;, because the substrate does not permit them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The academic connection: why this matters for teaching and for engineering
&lt;/h2&gt;

&lt;p&gt;This paper connects directly to the pedagogical work of Staroletov (2026), which emphasizes that Erlang’s supervision model is not a set of APIs to be memorized but a &lt;strong&gt;set of contracts to be understood&lt;/strong&gt;. The course described in that paper moves students from simple processes to OTP supervision to verification, treating each step as an exercise in reasoning about consistency, not just syntax.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Supervision Without Blocking&lt;/em&gt; makes the same point from the engineering side. Frameworks that copy the OTP style &lt;strong&gt;without evaluating which guarantees deserve keeping&lt;/strong&gt; are not implementing supervision. They are implementing a resemblance of supervision . The 24 defect classes are the cost of that resemblance.&lt;/p&gt;

&lt;p&gt;The lesson is not “never port OTP.” The lesson is: &lt;strong&gt;if you port it, you must reconstruct the atomicity explicitly — and you must know which guarantees your runtime can actually honor.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What this means for practitioners
&lt;/h2&gt;

&lt;p&gt;If you are building on the BEAM, this paper reinforces a simple principle: &lt;strong&gt;use native OTP&lt;/strong&gt;. The synchronous supervisor is not a legacy constraint. It is the mechanism that makes the consistency contracts true. Replacing it with an asynchronous variant inside Erlang would require the same reconstruction ANON performs — and OTP already does it correctly.&lt;/p&gt;

&lt;p&gt;If you are evaluating an actor framework on another runtime, ask three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What happens to lifecycle signals under backpressure?&lt;/strong&gt; If the answer involves “push” without an owned completion value, ordering is not guaranteed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How does the framework distinguish incarnations?&lt;/strong&gt; Without per-incarnation identity, a late signal from a dead process can be mistaken for a live one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What atomicity does the supervisor provide?&lt;/strong&gt; If the supervisor is asynchronous and does not explicitly reconstruct atomicity, the consistency contracts are not the same as OTP’s — regardless of what the README says.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The paper’s contribution is to make those questions askable, with evidence behind each answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: contracts, not cosmetics
&lt;/h2&gt;

&lt;p&gt;OTP supervision looks simple. A supervisor starts children, restarts them when they die, and escalates when it cannot recover. The simplicity is real — but it is &lt;strong&gt;earned by a runtime that blocks&lt;/strong&gt;. Ports that remove the blocking without replacing the atomicity are not simplifying supervision. They are removing the foundation and hoping the building stays up.&lt;/p&gt;

&lt;p&gt;Uwejan and Dogdu’s audit shows what happens when it does not. The 24 defect classes are not exotic edge cases. They are the predictable consequences of copying a pattern without its substrate. And the fix is not a patch — it is a redesign that treats every guarantee as a design decision that must earn its place.&lt;/p&gt;

&lt;p&gt;That is a lesson that applies far beyond Erlang.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Main reference:&lt;/strong&gt; UWEJAN, Sam; DOGDU, Erdogan. &lt;em&gt;Supervision Without Blocking: An Evidence-Driven Redesign of OTP-Style Fault Tolerance for Cooperative Runtimes&lt;/em&gt;. arXiv:2610.34521 [cs.DC], 25 Oct 2026. Available at: &lt;a href="https://arxiv.org/abs/2610.34521" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2610.34521&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related pedagogical context:&lt;/strong&gt; STAROLETOV, Sergey. &lt;em&gt;Modern Erlang Teaching from Code Refactoring to Actor System Verification&lt;/em&gt;. In: Proceedings of the 25th ACM SIGPLAN International Workshop on Erlang (Erlang ‘26), 2026. DOI: &lt;a href="https://doi.org/10.1145/3830434.3830946" rel="noopener noreferrer"&gt;10.1145/3830434.3830946&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Have you evaluated an “OTP-style” framework on a non-BEAM runtime? What guarantees did it actually provide — and what did you have to reconstruct yourself? Let me know in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>erlang</category>
    </item>
    <item>
      <title>Erlang Actors vs Java Virtual Threads: What Staroletov (2026) Teaches Us About Concurrency Design</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Thu, 05 Nov 2026 12:08:23 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/erlang-actors-vs-java-virtual-threads-what-staroletov-2026-teaches-us-about-concurrency-design-5e5p</link>
      <guid>https://dev.to/matheuscamarques/erlang-actors-vs-java-virtual-threads-what-staroletov-2026-teaches-us-about-concurrency-design-5e5p</guid>
      <description>&lt;h1&gt;
  
  
  Erlang Actors vs Java Virtual Threads: What Staroletov (2026) Teaches Us About Concurrency Design
&lt;/h1&gt;

&lt;p&gt;Java finally has lightweight threads. Erlang has had them since 1986. But the gap between the two is not about raw concurrency numbers — it is about what happens when things go wrong.&lt;/p&gt;

&lt;p&gt;In his 2026 Erlang Workshop paper &lt;em&gt;“Modern Erlang Teaching from Code Refactoring to Actor System Verification,”&lt;/em&gt; Sergey Staroletov draws a direct contrast between Erlang’s actor model and Java’s modern virtual threads (Project Loom). His point is not that one is faster. It is that &lt;strong&gt;both enable high concurrency, but only one bakes fault isolation and hierarchical supervision into the language design from the ground up.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article unpacks that contrast — and why it matters for anyone choosing a concurrency model today.&lt;/p&gt;




&lt;h2&gt;
  
  
  The concurrency renaissance
&lt;/h2&gt;

&lt;p&gt;For decades, the standard answer to “I need more concurrency” was “spawn more OS threads.” That answer collapsed under its own weight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each OS thread reserves &lt;strong&gt;1–8 MB&lt;/strong&gt; of stack by default.&lt;/li&gt;
&lt;li&gt;Context switching costs microseconds and pollutes CPU caches.&lt;/li&gt;
&lt;li&gt;Ten thousand threads is already a struggle on commodity hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two independent communities attacked this problem from opposite directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Java&lt;/strong&gt; kept the thread abstraction but made it cheap. Project Loom (finalized in JDK 21) introduced &lt;strong&gt;virtual threads&lt;/strong&gt;: JVM-managed threads multiplexed onto a small pool of carrier threads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Erlang&lt;/strong&gt; never had heavyweight threads to begin with. The BEAM virtual machine has always scheduled &lt;strong&gt;processes&lt;/strong&gt; — not OS threads — in user space, with preemption based on reduction counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On paper, both now support millions of concurrent units of execution. So why does Staroletov insist the models are fundamentally different?&lt;/p&gt;




&lt;h2&gt;
  
  
  What Java virtual threads actually are
&lt;/h2&gt;

&lt;p&gt;A virtual thread in Java is a &lt;code&gt;Thread&lt;/code&gt; object backed by the JVM rather than the OS. When a virtual thread blocks on I/O, the JVM &lt;strong&gt;unmounts&lt;/strong&gt; it from its carrier thread and parks it on the heap. The carrier is freed to run another virtual thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Executors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newVirtualThreadPerTaskExecutor&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;IntStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sleep&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a genuine leap forward. A million virtual threads sleeping for one second complete in roughly one second — not a million. The API is familiar: it is still &lt;code&gt;Thread&lt;/code&gt;, still &lt;code&gt;Runnable&lt;/code&gt;, still &lt;code&gt;ExecutorService&lt;/code&gt;. &lt;strong&gt;Migration cost is near zero.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But look closely at what a virtual thread &lt;em&gt;is&lt;/em&gt;: it is a &lt;strong&gt;task&lt;/strong&gt;, not an &lt;strong&gt;actor&lt;/strong&gt;. It does not have a mailbox. It does not have a PID. It cannot receive messages from another thread without an external queue. It shares mutable memory with every other thread in the JVM. And when it throws an uncaught exception, that exception propagates to whatever submitted it — usually an &lt;code&gt;ExecutorService&lt;/code&gt;, which may swallow it, log it, or hand it to an &lt;code&gt;UncaughtExceptionHandler&lt;/code&gt; you forgot to configure.&lt;/p&gt;

&lt;p&gt;Virtual threads make &lt;strong&gt;threads cheap&lt;/strong&gt;. They do not make &lt;strong&gt;failure cheap&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Erlang processes actually are
&lt;/h2&gt;

&lt;p&gt;An Erlang process is not a thread. It is a first-class language construct with three properties baked into the runtime:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Its own mailbox&lt;/strong&gt; — a queue of messages, addressed by PID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its own heap&lt;/strong&gt; — no shared mutable state with any other process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its own failure domain&lt;/strong&gt; — a crash in one process does not corrupt another.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;

&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;receive&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nv"&gt;From&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nv"&gt;From&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Pid&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt;
    &lt;span class="k"&gt;receive&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;New&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;New&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Pid&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;self&lt;/span&gt;&lt;span class="p"&gt;()},&lt;/span&gt;
    &lt;span class="k"&gt;receive&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The memory math is the headline number Staroletov cites: an Erlang process costs about &lt;strong&gt;326 words — roughly 2.6 KB on a 64-bit system&lt;/strong&gt;. Not kilobytes of stack. Not megabytes of reserved memory. A few kilobytes of heap and a small control block. A single BEAM node can hold millions of these comfortably.&lt;/p&gt;

&lt;p&gt;The BEAM scheduler runs entirely in &lt;strong&gt;user space&lt;/strong&gt;. It preempts processes based on a &lt;strong&gt;reduction count&lt;/strong&gt; — a measure of work done, not wall-clock time — which means a process cannot starve others by running a tight loop. No OS involvement, no thread priorities, no priority inversion.&lt;/p&gt;

&lt;p&gt;But the real story is not the memory. It is what happens when one of those processes crashes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real difference: fault isolation
&lt;/h2&gt;

&lt;p&gt;Here is the same failure scenario in both models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java virtual thread:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// throws ParseException&lt;/span&gt;
    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ParseException&lt;/code&gt; unmounts the virtual thread. The exception is captured by the &lt;code&gt;Future&lt;/code&gt; returned from &lt;code&gt;submit()&lt;/code&gt;. If nobody calls &lt;code&gt;future.get()&lt;/code&gt;, the exception is &lt;strong&gt;silently lost&lt;/strong&gt;. If the task was submitted to a shared executor, the executor may log it — or may not, depending on configuration. Meanwhile, the &lt;code&gt;socket&lt;/code&gt; is leaked, the &lt;code&gt;conn&lt;/code&gt; is leaked, and any state partially written during &lt;code&gt;parse()&lt;/code&gt; is now inconsistent.&lt;/p&gt;

&lt;p&gt;To handle this properly, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;try/catch&lt;/code&gt; around every task body.&lt;/li&gt;
&lt;li&gt;A resource-management strategy (&lt;code&gt;try-with-resources&lt;/code&gt; around &lt;code&gt;conn&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A policy for what to do with the failed task (retry, log, alert).&lt;/li&gt;
&lt;li&gt;A strategy for restarting the failed component.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Erlang process:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nf"&gt;handle_conn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;parse_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;accept_failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Reason&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;parse/1&lt;/code&gt; crashes, the process dies. Its mailbox is discarded. Its heap is garbage-collected as a unit. The socket is closed by the runtime because the process that owned it is gone. &lt;strong&gt;Nothing else in the system is affected.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And critically: &lt;strong&gt;a supervisor is watching.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Hierarchical supervision: OTP as a design pattern
&lt;/h2&gt;

&lt;p&gt;Erlang’s “let it crash” philosophy is not about ignoring failures. It is about making failures &lt;strong&gt;local&lt;/strong&gt; and &lt;strong&gt;recoverable&lt;/strong&gt; through a supervision tree.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn_sup&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;

&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;SupFlags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;intensity&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nv"&gt;ChildSpec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;conn_handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;conn_handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
                  &lt;span class="n"&gt;restart&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;shutdown&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;SupFlags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ChildSpec&lt;/span&gt;&lt;span class="p"&gt;]}}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This supervisor says: &lt;em&gt;“If the &lt;code&gt;conn_handler&lt;/code&gt; worker dies, restart it. If it dies more than five times in ten seconds, escalate to my own supervisor — because something is fundamentally wrong.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is the entire failure-handling story: &lt;strong&gt;one line of configuration&lt;/strong&gt; replaces pages of Java exception-handling boilerplate. The supervisor tree is not an afterthought bolted onto the runtime. It is the runtime’s organizing principle.&lt;/p&gt;

&lt;p&gt;Java has no equivalent in the language. You can build one — libraries like Akka do exactly this — but you are building it &lt;strong&gt;on top of&lt;/strong&gt; a runtime that was not designed for it. Staroletov’s point is that this difference is not incidental. It is architectural.&lt;/p&gt;




&lt;h2&gt;
  
  
  What virtual threads do not give you
&lt;/h2&gt;

&lt;p&gt;To be fair to Project Loom, it solves the problem it was designed to solve: &lt;strong&gt;thread-per-request scalability without rewriting blocking code&lt;/strong&gt;. That is a massive win for the Java ecosystem, and Staroletov acknowledges it.&lt;/p&gt;

&lt;p&gt;But the comparison he draws is deliberately precise. Virtual threads give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cheap concurrency.&lt;/li&gt;
&lt;li&gt;Familiar &lt;code&gt;Thread&lt;/code&gt; API.&lt;/li&gt;
&lt;li&gt;Seamless integration with existing &lt;code&gt;java.util.concurrent&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They do &lt;strong&gt;not&lt;/strong&gt; give you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolated failure domains.&lt;/strong&gt; A virtual thread shares the JVM heap with every other thread. A &lt;code&gt;StackOverflowError&lt;/code&gt; or &lt;code&gt;OutOfMemoryError&lt;/code&gt; takes down the whole process, not one task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mailboxes.&lt;/strong&gt; Communication is still through shared memory or external queues, not first-class message passing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supervision trees.&lt;/strong&gt; Failure recovery is an application-level concern, not a runtime primitive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location transparency.&lt;/strong&gt; A virtual thread cannot send a message to a thread on another node without a serialization layer you write yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Erlang gives you all four &lt;strong&gt;as language features&lt;/strong&gt;. That is the difference Staroletov is pointing at.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pedagogical angle
&lt;/h2&gt;

&lt;p&gt;The contrast appears in Staroletov’s course at a specific moment. After students have refactored imperative Erlang into functional Erlang, and after they have written recursive functions with multiple accumulators, the course introduces &lt;strong&gt;lightweight processes&lt;/strong&gt; through a live demonstration: a TCP server handling thousands of simultaneous connections on modest hardware.&lt;/p&gt;

&lt;p&gt;The pedagogical payoff is the &lt;strong&gt;memory comparison&lt;/strong&gt;. When students see that an Erlang process costs 2.6 KB while an OS thread costs megabytes, the actor model stops being an abstract concept and becomes a concrete engineering trade-off. When they then see a supervisor restart a crashed worker automatically, the “let it crash” philosophy stops being a slogan and becomes a design pattern they can reason about.&lt;/p&gt;

&lt;p&gt;The Java comparison enters as a &lt;strong&gt;foil&lt;/strong&gt;, not as a competitor. Staroletov uses modern Java virtual threads to show that high concurrency is now table stakes. What is &lt;em&gt;not&lt;/em&gt; table stakes — what still requires deliberate architectural choices in Java — is &lt;strong&gt;fault isolation and hierarchical supervision&lt;/strong&gt;. Erlang gives them to you on day one because the language was designed around them.&lt;/p&gt;




&lt;h2&gt;
  
  
  So which should you use?
&lt;/h2&gt;

&lt;p&gt;The honest answer is: &lt;strong&gt;it depends on what you are building.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You need…&lt;/th&gt;
&lt;th&gt;Lean toward&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Millions of blocking I/O tasks, existing Java codebase&lt;/td&gt;
&lt;td&gt;Java virtual threads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault-tolerant distributed systems, telecom-grade uptime&lt;/td&gt;
&lt;td&gt;Erlang/OTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared-memory parallelism on large datasets&lt;/td&gt;
&lt;td&gt;Java (or any JVM language)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location transparency and hot code reloading&lt;/td&gt;
&lt;td&gt;Erlang/OTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Familiar tooling, huge library ecosystem&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supervision trees as a first-class primitive&lt;/td&gt;
&lt;td&gt;Erlang/OTP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Virtual threads are a &lt;strong&gt;concurrency primitive&lt;/strong&gt;. Erlang processes are a &lt;strong&gt;system architecture&lt;/strong&gt;. They are not competing answers to the same question — they are answers to different questions that happen to overlap.&lt;/p&gt;

&lt;p&gt;Staroletov’s contribution is to make that distinction visible to students &lt;strong&gt;before&lt;/strong&gt; they commit to a mental model. That is what good teaching does: it does not tell you which tool to use. It tells you what each tool is actually for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Java virtual threads closed the concurrency gap. They did not close the &lt;strong&gt;fault-tolerance gap&lt;/strong&gt;. Erlang’s actor model offers something virtual threads structurally cannot: failure as a first-class, isolatable, supervisable event — not an exception that escapes to an executor.&lt;/p&gt;

&lt;p&gt;Staroletov (2026) uses this contrast not to declare a winner, but to sharpen a pedagogical point. When students see both models side by side, they stop asking “which is faster?” and start asking “what kind of failure do I want my system to survive?” That is the question that actually determines the architecture.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Main reference:&lt;/strong&gt; STAROLETOV, Sergey. &lt;em&gt;Modern Erlang Teaching from Code Refactoring to Actor System Verification&lt;/em&gt;. In: Proceedings of the 25th ACM SIGPLAN International Workshop on Erlang (Erlang ‘26), 2026. DOI: &lt;a href="https://doi.org/10.1145/3830434.3830946" rel="noopener noreferrer"&gt;10.1145/3830434.3830946&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Have you migrated a Java service to virtual threads, or an Erlang service to something else? What surprised you about the failure semantics? Let me know in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>erlang</category>
    </item>
    <item>
      <title>From `if` to `verify`: How to Actually Teach Erlang (from Refactoring to Actor System Verification)</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Thu, 05 Nov 2026 11:51:12 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/from-if-to-verify-how-to-actually-teach-erlang-from-refactoring-to-actor-system-verification-35fp</link>
      <guid>https://dev.to/matheuscamarques/from-if-to-verify-how-to-actually-teach-erlang-from-refactoring-to-actor-system-verification-35fp</guid>
      <description>&lt;h1&gt;
  
  
  From &lt;code&gt;if&lt;/code&gt; to &lt;code&gt;verify&lt;/code&gt;: How to Actually Teach Erlang (from Refactoring to Actor System Verification)
&lt;/h1&gt;

&lt;p&gt;Teaching Erlang is easy. The hard part is getting students to &lt;strong&gt;think&lt;/strong&gt; in Erlang.&lt;/p&gt;

&lt;p&gt;Anyone who has taught functional programming to students coming from C, Java, or Python knows the obstacle: the student writes Erlang with a Java accent. They use commas as statement separators, &lt;code&gt;if&lt;/code&gt; as a control-flow construct, temporary variables for everything, and at the end they ask: &lt;em&gt;“Professor, why doesn’t this feel functional?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The paper &lt;strong&gt;“Modern Erlang Teaching from Code Refactoring to Actor System Verification”&lt;/strong&gt; by Sergey Staroletov (Polzunov Altai State Technical University), presented at the &lt;em&gt;Erlang Workshop 2026&lt;/em&gt; (co-located with ICFP), synthesizes years of experience teaching Erlang in a Master’s program in Software Engineering and proposes a methodological progression that goes from refactoring imperative code all the way to formal verification of actor systems with Promela/SPIN.&lt;/p&gt;

&lt;p&gt;In this article, I’ll break down that progression and show why it works in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem: the “imperative accent” in Erlang code
&lt;/h2&gt;

&lt;p&gt;The typical Erlang student already knows how to program in imperative languages. They are used to sequential execution, mutable state, and explicit control flow. When they write Erlang, they reproduce those patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nf"&gt;process_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nv"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;FullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Parent&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;IsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;filelib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;
        &lt;span class="nv"&gt;IsDir&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;recurser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;IsDir&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;FullName&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;~n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;process_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;process_one&lt;/span&gt;&lt;span class="p"&gt;(_,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This code &lt;strong&gt;works&lt;/strong&gt;, but it violates the principles of functional style: there are commas separating “commands,” an imperative &lt;code&gt;if&lt;/code&gt;, and temporary variables that create hidden temporal dependencies. The compiler cannot safely reorder those expressions, and the student does not see the benefits in parallelization and testability that the functional paradigm offers.&lt;/p&gt;

&lt;p&gt;Staroletov’s proposal is to &lt;strong&gt;systematically refactor&lt;/strong&gt; this code until commas as sequential operators are eliminated and replaced by function composition.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: Refactoring — eliminate the commas
&lt;/h2&gt;

&lt;p&gt;The goal is to transform the body of each function into &lt;strong&gt;a single expression&lt;/strong&gt;, composed of calls to smaller functions.&lt;/p&gt;

&lt;p&gt;The methodology has five steps applied to the original code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isolate side effects&lt;/strong&gt; (I/O, process dictionary access, file writes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Introduce intermediate functions&lt;/strong&gt; for each expression evaluated sequentially.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass the result of the previous evaluation as a parameter&lt;/strong&gt; to the next function, instead of storing it in a temporary variable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace &lt;code&gt;if&lt;/code&gt; with pattern matching&lt;/strong&gt; (different function clauses for different guards or types).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eliminate code duplication&lt;/strong&gt; by moving repeated computations into separate functions and then composing them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Applied to the example:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="c"&gt;%% Step 1: isolate construction of the full name
&lt;/span&gt;&lt;span class="nf"&gt;construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Parent&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;%% Steps 2 and 3: eliminate FullName and IsDir by passing the result forward
&lt;/span&gt;&lt;span class="nf"&gt;process_by_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;recurser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;process_by_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;FullName&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;~n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="c"&gt;%% Step 4: pattern matching instead of if
&lt;/span&gt;&lt;span class="nf"&gt;process_construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;process_by_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;filelib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Full&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nv"&gt;Full&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="c"&gt;%% Step 5: compose everything
&lt;/span&gt;&lt;span class="nf"&gt;process_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nv"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;process_construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;First&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;process_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;process_one&lt;/span&gt;&lt;span class="p"&gt;(_,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The final code is a &lt;strong&gt;chain of compositions&lt;/strong&gt; and accumulator recursion. There are no commas separating “commands” — only expressions that compose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this work pedagogically?&lt;/strong&gt; Because the student &lt;strong&gt;sees the transformation happen&lt;/strong&gt;. They do not receive a ready-made solution; they refactor the code they wrote themselves, step by step, until the functional style emerges naturally.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Recursion with Taylor series — breaking the factorial/Fibonacci mold
&lt;/h2&gt;

&lt;p&gt;After refactoring, the next challenge is &lt;strong&gt;recursion&lt;/strong&gt;. The classic examples — factorial, Fibonacci, list sum — are useful, but they do not reveal the depth of recursive thinking.&lt;/p&gt;

&lt;p&gt;Staroletov proposes computing &lt;strong&gt;sine via the Taylor series&lt;/strong&gt; (Maclaurin):&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mop"&gt;sin&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mclose"&gt;!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;5&lt;/span&gt;&lt;span class="mclose"&gt;!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;7&lt;/span&gt;&lt;span class="mclose"&gt;!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;7&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="minner"&gt;…&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;The student implements a recursive function that accumulates two parameters: the &lt;strong&gt;partial sum&lt;/strong&gt; and the &lt;strong&gt;current term&lt;/strong&gt;, with &lt;strong&gt;two stopping criteria&lt;/strong&gt; — precision 
&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;ϵ&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
 and a maximum number of iterations.&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nf"&gt;sin_n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;IMax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;EPS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;IMax&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;EPS&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;sin_n&lt;/span&gt;&lt;span class="p"&gt;(_&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;IMax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;EPS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;I&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nv"&gt;IMax&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;sin_n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;IMax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;EPS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;sin_n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;Sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;Current&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;X&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;X&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nv"&gt;I&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
          &lt;span class="nv"&gt;I&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;IMax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;EPS&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;sin_e&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sin_n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00001&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This activity forces the student to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;accumulate more than one parameter&lt;/strong&gt; (sum and current term), expanding the concept of an accumulator;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;compute the recurrence factor&lt;/strong&gt; correctly;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;handle two stopping criteria&lt;/strong&gt; (protection against divergent series);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;understand numerical limitations&lt;/strong&gt; (for large 
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;∣&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mord"&gt;∣&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;, the series converges slowly, requiring argument reduction).&lt;/p&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The method is reinforced with other problems that use the same scheme — computing 
&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;π&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
, computing 
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
, manipulating lists &lt;strong&gt;without using the ready-made functions from the &lt;code&gt;lists&lt;/code&gt; module&lt;/strong&gt; — and each function must be accompanied by unit tests with EUnit.




&lt;h2&gt;
  
  
  Step 3: Lightweight processes — the “wow” that changes perspective
&lt;/h2&gt;

&lt;p&gt;After consolidating the functional core, the course introduces the actor model. The paper highlights an impactful demonstration: a &lt;strong&gt;TCP server in Erlang&lt;/strong&gt; that handles &lt;strong&gt;thousands of simultaneous connections on modest hardware&lt;/strong&gt;, briefly compared with &lt;strong&gt;modern Java virtual threads&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The pedagogical point is the &lt;strong&gt;order of magnitude&lt;/strong&gt;: Erlang processes use about &lt;strong&gt;326 words of memory&lt;/strong&gt; (2.6 KB on 64-bit), versus 1–8 MB per operating-system thread. The BEAM scheduler runs in user space, preempting processes based on reduction counts. The student sees in practice why the actor model scales where traditional threads collapse.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Verification — Promela as a logical continuation of the actor model
&lt;/h2&gt;

&lt;p&gt;Here is the most interesting methodological differentiator in the paper. Instead of treating formal verification as a separate and “scary” topic, Staroletov presents it as a &lt;strong&gt;natural continuation&lt;/strong&gt; of studying actors.&lt;/p&gt;

&lt;p&gt;The idea is simple: if the student already thinks in terms of &lt;strong&gt;processes&lt;/strong&gt;, &lt;strong&gt;channels&lt;/strong&gt;, and &lt;strong&gt;message passing&lt;/strong&gt;, they already have the mental framework to model those same concepts in &lt;strong&gt;Promela&lt;/strong&gt; and verify them with &lt;strong&gt;SPIN&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The mapping is almost direct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Erlang processes → Promela processes;&lt;/li&gt;
&lt;li&gt;PIDs → &lt;strong&gt;dedicated channels&lt;/strong&gt; between processes (since Promela has no direct equivalent to PIDs);&lt;/li&gt;
&lt;li&gt;message send/receive → channel operations (&lt;code&gt;!&lt;/code&gt; and &lt;code&gt;?&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The student formulates &lt;strong&gt;LTL properties&lt;/strong&gt; and verifies them in SPIN. The basic set includes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;LTL formula&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Absence of deadlock&lt;/td&gt;
&lt;td&gt;Checked automatically by SPIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reachability&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;⋄&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;msgRcv&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safety (“message never sent before connection”)&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord amsrm"&gt;□&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;¬&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;msgSent&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;∧&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;¬&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;connected&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Liveness (“if sent, eventually received”)&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord amsrm"&gt;□&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;msgSent&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;→&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;⋄&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;msgRcv&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absence of unauthorized states&lt;/td&gt;
&lt;td&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord amsrm"&gt;□&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;¬&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;bug&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SPIN builds the automaton and returns either &lt;strong&gt;“no errors”&lt;/strong&gt; or a &lt;strong&gt;counterexample&lt;/strong&gt; with the sequence of transitions that violates the property. The classic teaching example is a &lt;strong&gt;modified ping-pong&lt;/strong&gt;: the &lt;code&gt;Pong&lt;/code&gt; process sends a request to the &lt;code&gt;Right&lt;/code&gt; process before replying, but &lt;code&gt;Right&lt;/code&gt; “forgets” to respond. The property “every ping results in a pong” is violated, and the counterexample shows &lt;code&gt;Pong&lt;/code&gt; waiting forever.&lt;/p&gt;

&lt;p&gt;The student then &lt;strong&gt;sees the bug happen&lt;/strong&gt; in the simulator — and understands why verification is not an academic luxury, but part of engineering culture: &lt;em&gt;“it is not enough to write the system; you must analyze it and prove its properties.”&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: The end-to-end lab — a system that evolves
&lt;/h2&gt;

&lt;p&gt;All these topics are not isolated exercises. The course ties them together in a &lt;strong&gt;single lab cycle&lt;/strong&gt;, in which students develop &lt;strong&gt;the same distributed system&lt;/strong&gt; throughout the semester:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They start with &lt;strong&gt;simple processes&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Evolve to &lt;strong&gt;OTP supervision&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Integrate &lt;strong&gt;Java via JInterface&lt;/strong&gt; (to demonstrate the contrast between Erlang’s declarative style with pattern matching and Java code loaded with object transformations);&lt;/li&gt;
&lt;li&gt;Add a &lt;strong&gt;case study of a simplified PoET consensus protocol&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Verify the model in &lt;strong&gt;Promela/SPIN&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Optionally port it to &lt;strong&gt;Cloud Haskell&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This continuity is powerful: the student does not see “refactoring,” “recursion,” “actors,” and “verification” as disconnected subjects. They see &lt;strong&gt;a system growing&lt;/strong&gt; — and each new technique solves a real problem that emerges from that evolution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus: Erlang in the age of generative AI
&lt;/h2&gt;

&lt;p&gt;The paper also addresses a contemporary challenge: &lt;strong&gt;how do you teach programming when the student can ask ChatGPT for code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Staroletov’s answer is methodological, not prohibitive: &lt;strong&gt;live coding&lt;/strong&gt; and &lt;strong&gt;incremental code construction&lt;/strong&gt; as a counterpoint to the uncritical use of AI-generated code. The student needs to &lt;strong&gt;see the code being built&lt;/strong&gt;, understand every transformation, every design decision, every refactoring. The focus is not the final product, but the &lt;strong&gt;reasoning process&lt;/strong&gt; that leads to it.&lt;/p&gt;

&lt;p&gt;Interestingly, verification in Promela benefits from this as well: even if the student uses AI to &lt;strong&gt;translate&lt;/strong&gt; Erlang code into Promela, they still have to do the manual work of defining LTL properties, interpreting counterexamples, and adjusting the model. AI can accelerate the mechanical part, but it does not replace engineering judgment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: a progression that respects the student’s cognition
&lt;/h2&gt;

&lt;p&gt;What makes this approach remarkable is not only the content — refactoring, Taylor series, lightweight processes, Promela — but the &lt;strong&gt;order&lt;/strong&gt; in which everything appears. Each stage &lt;strong&gt;prepares the cognitive ground&lt;/strong&gt; for the next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring&lt;/strong&gt; eliminates the imperative habit and forces the student to think in terms of composition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mathematical recursion&lt;/strong&gt; expands the repertoire beyond trivial examples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight processes&lt;/strong&gt; provide the practical reward: “look at what this model enables.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification in Promela&lt;/strong&gt; crowns the structure: the student discovers that they can &lt;strong&gt;prove&lt;/strong&gt; properties about the system they built.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is a progression that goes from &lt;strong&gt;“how to write”&lt;/strong&gt; to &lt;strong&gt;“how to think,”&lt;/strong&gt; and from &lt;strong&gt;“how to think”&lt;/strong&gt; to &lt;strong&gt;“how to guarantee it is correct.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For anyone teaching Erlang — or any functional language with an actor model — Staroletov’s paper is essential reading. Not because it presents new tools, but because it organizes &lt;strong&gt;decades of pedagogical wisdom&lt;/strong&gt; into a coherent path, tested in the classroom and grounded in the cognitive reality of students.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Main reference:&lt;/strong&gt; STAROLETOV, Sergey. &lt;em&gt;Modern Erlang Teaching from Code Refactoring to Actor System Verification&lt;/em&gt;. In: Proceedings of the 25th ACM SIGPLAN International Workshop on Erlang (Erlang ‘26), 2026. DOI: &lt;a href="https://doi.org/10.1145/3830434.3830946" rel="noopener noreferrer"&gt;10.1145/3830434.3830946&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;If you have ever taught Erlang (or learned it), what was the biggest cognitive “click” in your journey? Let me know in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>elixir</category>
    </item>
    <item>
      <title>Practical Erlang/OTP Tutorial Based on Recent Academic Evidence (2025–2026)</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Tue, 03 Nov 2026 13:50:47 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/practical-erlangotp-tutorial-based-on-recent-academic-evidence-2025-2026-4io</link>
      <guid>https://dev.to/matheuscamarques/practical-erlangotp-tutorial-based-on-recent-academic-evidence-2025-2026-4io</guid>
      <description>&lt;h2&gt;
  
  
  Practical Erlang/OTP Tutorial Based on Recent Academic Evidence (2025–2026)
&lt;/h2&gt;

&lt;p&gt;Based on an in-depth search of recent academic articles (2025–2026), I have prepared a practical Erlang/OTP tutorial. The material integrates the main findings from the literature: from refactoring imperative code into the functional paradigm to formal verification of actor systems and the redesign of supervision mechanisms for cooperative runtimes. The tutorial is structured in progressive stages, each grounded in academic evidence.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Why Erlang? The Academic Rationale
&lt;/h3&gt;

&lt;p&gt;Modern distributed systems require reliability, scalability, and fault tolerance. Classical imperative languages, with shared memory and explicit threads, often prove inadequate for systems designed for thousands of nodes and millions of concurrent connections. The functional paradigm, which discourages mutable state, offers a cleaner path to parallelization.&lt;/p&gt;

&lt;p&gt;Erlang occupies a special place among functional languages because it directly implements the &lt;strong&gt;actor model&lt;/strong&gt; proposed at MIT in the 1970s. In this model, a system consists of processes (actors) that interact exclusively through asynchronous messages. Each actor has a unique address (PID), can create other actors, send messages, and change its behavior. Unlike CSP (Communicating Sequential Processes), which uses synchronous channels, the actor model is better suited to fully decentralized and fault-tolerant systems.&lt;/p&gt;

&lt;p&gt;A crucial technical differentiator is memory cost: Erlang’s &lt;strong&gt;lightweight processes&lt;/strong&gt; consume only about 326 words (approximately 2.6 KB on 64-bit systems), compared with 1–8 MB per operating system thread — three orders of magnitude less. This allows the BEAM VM to manage millions of simultaneous processes with efficient resource use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Supporting reference:&lt;/strong&gt; STAROLETOV, Sergey. &lt;em&gt;Modern Erlang Teaching from Code Refactoring to Actor System Verification&lt;/em&gt;. In: Proceedings of the 25th ACM SIGPLAN International Workshop on Erlang (Erlang '26), 2026. This article synthesizes years of experience teaching Erlang and proposes a methodological progression that goes from refactoring imperative code to verifying actor systems.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  2. First Steps: Refactoring from Imperative to Functional
&lt;/h3&gt;

&lt;p&gt;The biggest obstacle for beginners in Erlang is not syntax, but the &lt;strong&gt;cognitive shift&lt;/strong&gt; from imperative to functional thinking. An effective technique proposed in the literature is &lt;strong&gt;systematic refactoring&lt;/strong&gt; of imperative code, eliminating sequential operators (the “commas”) and replacing them with recursion and pattern matching.&lt;/p&gt;

&lt;h4&gt;
  
  
  Exercise 1: From an imperative loop to a recursive function
&lt;/h4&gt;

&lt;p&gt;Consider the following Java (or Python) code that calculates the sum of the first ( N ) integers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Erlang, the same logic is expressed with &lt;strong&gt;tail recursion&lt;/strong&gt; and &lt;strong&gt;pattern matching&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;

&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Acc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Acc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Acc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;N&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function &lt;code&gt;sum/2&lt;/code&gt; is tail-recursive: the recursive call is the last operation, allowing the BEAM compiler to optimize it into an efficient loop without stack growth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Academic connection:&lt;/strong&gt; Staroletov’s article (2026) reports that this approach — removing the “comma mindset” and introducing recursion through Taylor series — transforms students’ understanding of recursion and pattern matching. The author also compares Erlang’s declarative style with Java code laden with object transformations via JInterface, demonstrating how the absence of mutable state simplifies reasoning about concurrent programs.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Lightweight Processes and Massive Concurrency
&lt;/h3&gt;

&lt;p&gt;Concurrency in Erlang is not an add-on: it is a first-class citizen. Each process is isolated, with no shared memory, and communication occurs exclusively through asynchronous message passing. This eliminates race conditions and the need for locks and mutexes.&lt;/p&gt;

&lt;h4&gt;
  
  
  Exercise 2: A TCP server that supports thousands of connections
&lt;/h4&gt;

&lt;p&gt;Staroletov’s article highlights the demonstration of lightweight processes through a TCP server that withstands an enormous number of connections on modest hardware. Let us build a minimal echo server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;echo_server&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;

&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ListenSocket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}]),&lt;/span&gt;
    &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ListenSocket&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ListenSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ListenSocket&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nb"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;ListenSocket&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nn"&gt;gen_tcp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;handle_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;ok&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each accepted connection is handled by a &lt;strong&gt;new process&lt;/strong&gt; created with &lt;code&gt;spawn/1&lt;/code&gt;. Since each process consumes only a few kilobytes, tens of thousands of simultaneous connections are viable on a single machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comparison with Java threads:&lt;/strong&gt; Staroletov (2026) contrasts this approach with Java’s modern virtual threads, showing that although both enable high concurrency, Erlang’s actor model offers fault isolation and hierarchical supervision from the language design onward.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. OTP: Supervision and Fault Tolerance in Practice
&lt;/h3&gt;

&lt;p&gt;OTP (Open Telecom Platform) is the set of libraries and patterns that makes Erlang an industrial-grade platform. The central concept is &lt;strong&gt;supervision&lt;/strong&gt;: a supervisor process monitors child processes and, when one of them fails, applies a restart strategy (one-for-one, one-for-all, rest-for-one, simple-one-for-one).&lt;/p&gt;

&lt;h4&gt;
  
  
  Exercise 3: A supervision tree for a counter
&lt;/h4&gt;

&lt;p&gt;Let us create a simple counter server with supervision:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_call&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_cast&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;

&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;

&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;gen_server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;

&lt;span class="nf"&gt;handle_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;noreply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;

&lt;span class="nf"&gt;handle_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt;&lt;span class="nv"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;State&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the supervisor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter_supervisor&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;behaviour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="ni"&gt;export&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;

&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;supervisor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="nv"&gt;MODULE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nv"&gt;Child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]},&lt;/span&gt;
              &lt;span class="n"&gt;restart&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;permanent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;shutdown&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;modules&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="n"&gt;one_for_one&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Child&lt;/span&gt;&lt;span class="p"&gt;]}}.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;one_for_one&lt;/code&gt; strategy with &lt;code&gt;MaxR = 5&lt;/code&gt; and &lt;code&gt;MaxT = 10&lt;/code&gt; means that if the counter fails more than 5 times in 10 seconds, the supervisor also fails and propagates the error to the next hierarchical level — the “let it crash” principle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Academic connection:&lt;/strong&gt; The article &lt;em&gt;Supervision Without Blocking&lt;/em&gt; (2026) provides a careful audit of OTP’s supervision guarantees and proposes a redesign for cooperative runtimes. The authors identify 24 classes of defects in supervision implementations that copy the OTP style without understanding its consistency contracts, which presuppose a synchronous, single-threaded supervisor. The article validates the original OTP model but warns that ports to other languages must explicitly reconstruct the atomicity that Erlang’s synchronous supervisor obtains implicitly. This reinforces the importance of using native OTP rather than trying to replicate it superficially.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Verification of Actor Systems: Going Beyond Testing
&lt;/h3&gt;

&lt;p&gt;Actor systems are notoriously difficult to verify through traditional testing, given the nondeterminism inherent in message ordering. Recent literature offers two complementary approaches: &lt;strong&gt;runtime verification&lt;/strong&gt; and &lt;strong&gt;static protocol verification&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  5.1. Runtime Verification with ACTORCHESTRA
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;ACTORCHESTRA&lt;/strong&gt; tool (2025) allows specifying properties that span multiple actors and performing causality-sensitive verification. It defines a specification language, WALTZ, and generates monitoring executables in Erlang, injecting code surgically into the system to preserve causal relationships between actors.&lt;/p&gt;

&lt;p&gt;A conceptual example of a verifiable property: “whenever the &lt;code&gt;order&lt;/code&gt; actor sends a &lt;code&gt;confirmation&lt;/code&gt; message to the &lt;code&gt;customer&lt;/code&gt; actor, the &lt;code&gt;inventory&lt;/code&gt; actor must have received a &lt;code&gt;reservation&lt;/code&gt; message beforehand.” ACTORCHESTRA monitors this causal chain at runtime and alerts if the property is violated.&lt;/p&gt;

&lt;h4&gt;
  
  
  5.2. Formal Deadlock Detection with DDMon
&lt;/h4&gt;

&lt;p&gt;The article &lt;em&gt;Correct Black-Box Monitors for Distributed Deadlock Detection&lt;/em&gt; (2025) presents distributed monitors that detect deadlocks by observing only incoming and outgoing messages, without instrumenting the services’ source code. The formal model captures RPC-based application styles, such as &lt;code&gt;gen_servers&lt;/code&gt; in Erlang/OTP, and the monitoring algorithm is proven &lt;strong&gt;sound&lt;/strong&gt; (no false positives) and &lt;strong&gt;complete&lt;/strong&gt; (no false negatives). The DDMon tool implements these monitors for Erlang/OTP applications and demonstrates viable performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical application in the tutorial:&lt;/strong&gt; After building the counter system with supervision, you can attach DDMon to monitor for possible deadlocks. Although the counter example is too simple to generate deadlocks, the pattern applies to systems with &lt;code&gt;gen_server&lt;/code&gt; processes making chained synchronous calls — a common scenario in microservices.&lt;/p&gt;

&lt;h4&gt;
  
  
  5.3. Static Protocol Typing with NVLang
&lt;/h4&gt;

&lt;p&gt;The &lt;em&gt;NVLang&lt;/em&gt; article (2025) addresses a fundamental limitation of Erlang: the absence of static guarantees about message protocols. Processes communicate by sending arbitrary messages that are filtered by pattern matching at runtime, deferring protocol violations to production failures.&lt;/p&gt;

&lt;p&gt;NVLang proposes a statically typed functional language that compiles to Core Erlang, preserving interoperability with the existing ecosystem. The central contribution is that &lt;strong&gt;algebraic data types (ADTs)&lt;/strong&gt; naturally encode actor protocols: each actor declares the sum type that represents its message vocabulary, and the type system enforces conformance at compile time. Typed process identifiers (&lt;code&gt;Pid[τ]&lt;/code&gt;) and typed futures (&lt;code&gt;Future[τ]&lt;/code&gt;) provide safe request-reply patterns.&lt;/p&gt;

&lt;p&gt;Although NVLang is a separate language, the tutorial can use its concepts to &lt;strong&gt;discipline protocol design&lt;/strong&gt; in pure Erlang: before implementing a &lt;code&gt;gen_server&lt;/code&gt;, explicitly define the sum type of messages it accepts. This reduces the class of errors that would only manifest in production.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Integrative Case Study: A SEDA Web Server in Erlang
&lt;/h3&gt;

&lt;p&gt;The &lt;em&gt;ERLDA&lt;/em&gt; article (2025) proposes a scalable and resilient web server using the &lt;strong&gt;SEDA (Staged Event-Driven Architecture)&lt;/strong&gt; with Erlang and OTP. The motivation is that traditional web servers based on synchronous TCP have significant limitations in high-demand scenarios, especially when managing millions of simultaneous connections.&lt;/p&gt;

&lt;p&gt;The SEDA architecture decomposes processing into stages connected by event queues, each stage executed by a pool of lightweight processes. In Erlang, this maps naturally to processes communicating through messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dot"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Acceptor&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Processor&lt;/span&gt; &lt;span class="nv"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Response&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;Writer&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each arrow represents a process (or pool of processes) that consumes from one queue and produces to the next. OTP supervision ensures that the failure of one stage does not bring down the entire system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suggested final exercise:&lt;/strong&gt; Implement a minimal HTTP server with three stages: (1) an acceptor that receives connections and enqueues requests; (2) a pool of processes that handles the request (for example, generates a simple JSON response); (3) a writer that sends the response back to the client. Use &lt;code&gt;supervisor&lt;/code&gt; to ensure that each stage is restarted in case of failure.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Conclusion and Study Roadmap
&lt;/h3&gt;

&lt;p&gt;This tutorial started from the academic premise that Erlang is not merely a language, but a &lt;strong&gt;computational model&lt;/strong&gt; for reliable distributed systems. The suggested progression — functional refactoring → lightweight processes → OTP supervision → formal verification → SEDA architecture — is supported by evidence from recent research.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consolidated roadmap:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 1–2:&lt;/strong&gt; Refactoring imperative code into functional code (Exercise 1). Focus on tail recursion, pattern matching, and immutability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 3–4:&lt;/strong&gt; Lightweight processes and message passing (Exercise 2). Comparison with threads and virtual threads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 5–6:&lt;/strong&gt; OTP: &lt;code&gt;gen_server&lt;/code&gt;, supervisors, and restart strategies (Exercise 3). Study of OTP consistency guarantees and their limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 7–8:&lt;/strong&gt; Verification: ACTORCHESTRA for runtime verification and NVLang concepts for protocol typing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weeks 9–10:&lt;/strong&gt; SEDA case study (ERLDA) and construction of a concurrent web server with OTP.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recent literature confirms that Erlang/OTP remains a reference platform for systems that require “legendary reliability” — from telecommunications to messaging platforms with millions of simultaneous connections. Investing in learning its foundations is therefore not only technically sound but also academically well grounded.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References to the articles used:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;STAROLETOV, Sergey. &lt;em&gt;Modern Erlang Teaching from Code Refactoring to Actor System Verification&lt;/em&gt;. Erlang '26, 2026.&lt;/li&gt;
&lt;li&gt;UWEJAN, Sam; DOGDU, Erdogan. &lt;em&gt;Supervision Without Blocking: An Evidence-Driven Redesign of OTP-Style Fault Tolerance for Cooperative Runtimes&lt;/em&gt;. arXiv:2610.34521, 2026.&lt;/li&gt;
&lt;li&gt;GUERREIRO, Miguel de Oliveira. &lt;em&gt;NVLang: Unified Static Typing for Actor-Based Concurrency on the BEAM&lt;/em&gt;. arXiv:2512.05224, 2025.&lt;/li&gt;
&lt;li&gt;ROWICKI, Radosław Jan; FRANCALANZA, Adrian; SCALAS, Alceste. &lt;em&gt;Correct Black-Box Monitors for Distributed Deadlock Detection: Formalisation and Implementation&lt;/em&gt;. PACMPL, 2025.&lt;/li&gt;
&lt;li&gt;MIKYTIV, Vladyslav. &lt;em&gt;ACTORCHESTRA: a tool for catching faulty symphonies&lt;/em&gt;. Master’s Dissertation, Universidade Nova de Lisboa, 2025.&lt;/li&gt;
&lt;li&gt;AREIAS, Fernando. &lt;em&gt;ERLDA: Exploring Concurrency and Resilience with SEDA in Erlang&lt;/em&gt;. UTFPR, 2025.&lt;/li&gt;
&lt;li&gt;RIBEIRO, Nuno Dinis Gonçalves. &lt;em&gt;Comparative Analysis of Fault Tolerance in Elixir and Other Distributed and Concurrent Languages&lt;/em&gt;. ISEP, 2025.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>elixir</category>
      <category>erlang</category>
    </item>
    <item>
      <title>Erlang in 2026: OTP 29, Automotive Expansion, and the Talent Gap Keeping Salaries at the Top</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Tue, 03 Nov 2026 13:37:14 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/erlang-in-2026-otp-29-automotive-expansion-and-the-talent-gap-keeping-salaries-at-the-top-39gp</link>
      <guid>https://dev.to/matheuscamarques/erlang-in-2026-otp-29-automotive-expansion-and-the-talent-gap-keeping-salaries-at-the-top-39gp</guid>
      <description>&lt;p&gt;Erlang in 2026: OTP 29, Automotive Expansion, and the Talent Gap Keeping Salaries at the Top&lt;/p&gt;

&lt;p&gt;Erlang, the language created by Ericsson in 1986 for telecommunications systems, remains relevant in 2026, driven by constant OTP (Open Telecom Platform) updates, new portability to automotive systems, and demand for developers that far exceeds supply. The current landscape shows a mature language that is nevertheless evolving actively, whose principles of massive concurrency, fault tolerance, and hot swapping continue to support critical systems at companies such as WhatsApp, Discord, Goldman Sachs, and Amazon.&lt;/p&gt;

&lt;p&gt;OTP 29: What’s Coming&lt;br&gt;
The first Release Candidate for Erlang/OTP 29.0 was made available on February 11, 2026, with three RCs expected before the final release. The version brings significant changes to the language and runtime.&lt;/p&gt;

&lt;p&gt;Among the highlights is the implementation of native records (EEP-79). Unlike traditional tuples used as records, native records are a real data type, although they are still considered experimental in OTP 29 and possibly in OTP 30.&lt;/p&gt;

&lt;p&gt;Another addition is the guard BIF is_integer/3, which makes it possible to concisely check whether a value is an integer within a specific range, such as is_integer(I, 0, 100).&lt;/p&gt;

&lt;p&gt;Multivalue comprehensions (EEP-78) are also supported. The expression [-I, I || I &amp;lt;- [1, 2, 3]] produces [-1, 1, -2, 2, -3, 3]. In addition, by enabling the compr_assign feature, it becomes possible to bind variables inside comprehensions.&lt;/p&gt;

&lt;p&gt;On the compiler and JIT side, OTP 29 generates more efficient code for matching and creating binaries with multiple little-endian segments, and for map comprehensions with constant values that do not depend on the generator.&lt;/p&gt;

&lt;p&gt;OTP 28: Priority Messages&lt;br&gt;
OTP 28, released in May 2025, introduced priority messages, an opt-in mechanism that allows urgent messages to be inserted ahead of ordinary messages in a process mailbox. In systems with long queues, this makes it possible for critical signals—such as overload alerts or shutdown commands—to be read immediately, without waiting for the entire queue to be processed.&lt;/p&gt;

&lt;p&gt;QNX Portability: Erlang in Automotive&lt;br&gt;
In January 2026, a three-day effort resulted in the successful port of Erlang to QNX, a real-time operating system widely used in automotive and embedded systems from BlackBerry. The initiative, described as “a small nerd-snipe that actually worked,” expands Erlang’s reach into a domain where real-time and reliability requirements are even stricter.&lt;/p&gt;

&lt;p&gt;Security: Recent Vulnerabilities and the Importance of Updates&lt;br&gt;
Despite its architectural robustness, Erlang/OTP is not immune to implementation flaws. In September 2026, Ubuntu published advisory USN-8827-1 detailing several fixed vulnerabilities, including:&lt;/p&gt;

&lt;p&gt;CVE-2026-42792: a flaw in the Erlang Port Mapper Daemon that allowed denial-of-service attacks through slow connections.&lt;/p&gt;

&lt;p&gt;CVE-2026-75538: a buffer overflow in the Megaco flex scanner, with potential for arbitrary code execution.&lt;/p&gt;

&lt;p&gt;CVE-2026-55953: improper handling of certificate chains, leading to excessive resource consumption.&lt;/p&gt;

&lt;p&gt;The Canadian Centre for Cyber Security also issued an alert (AV26-948) in September 2026, listing affected versions and recommending immediate application of updates. This reinforces that, even in a language designed for fault tolerance, security hygiene—keeping OTP up to date—remains essential.&lt;/p&gt;

&lt;p&gt;The Market: Talent Scarcity and Salaries at the Top&lt;br&gt;
The combination of critical legacy systems and low adoption by new developers has created a peculiar economic scenario. According to Stack Overflow 2025 data, Erlang developers had the highest median salary among all languages: US$152,782, ahead of Python (US$120,000), JavaScript (US$110,000), and even Rust (US$145,000). This was the fourth consecutive year in which Erlang topped the salary ranking.&lt;/p&gt;

&lt;p&gt;The paradox is that only about 1.2% of developers use Erlang. The explanation lies in supply and demand dynamics: companies with Erlang systems that cannot be rewritten without astronomical risk—such as WhatsApp (2 billion users), Discord (5 million simultaneous voice connections), Goldman Sachs (trading systems), and parts of AWS that require 99.999% uptime—pay a premium for qualified professionals, while almost nobody learns the language.&lt;/p&gt;

&lt;p&gt;A recruiter at a financial company told the DevGenius blog that he spent eight months trying to hire an Erlang developer without a single qualified candidate; the team’s current professional is 62 years old and plans to retire in three years. This mismatch between the existing codebase and the flow of new talent is perhaps the biggest practical challenge for the Erlang ecosystem over the next decade.&lt;/p&gt;

&lt;p&gt;Why Erlang Still Matters&lt;br&gt;
In a 2025 talk, Ukrainian developer Viacheslav Katsuba argued that Erlang/OTP, at almost 40 years old, “continues to power some of the most demanding real-time systems in the world,” with built-in fault tolerance, massive concurrency, and hot code updates that remain unmatched for reliable distributed applications.&lt;/p&gt;

&lt;p&gt;Erlang’s lightweight processes are a technical differentiator that is difficult to replicate: each process consumes only about 326 words of memory (approximately 2.6 KB on 64-bit systems), three orders of magnitude less than the typical 1–8 MB of an operating system thread. Combined with the actor model—isolated processes, no shared memory, communicating through asynchronous messages—this makes it possible to build massively scalable systems without the heavy lock and mutex mechanisms common in other languages.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
Erlang in 2026 is not a telecommunications relic: it is an evolving platform, with an active release pipeline (OTP 29 on the way), expansion into new domains such as automotive via QNX, and a base of critical applications that guarantees continued demand for specialists. The shortage of professionals, however, represents both a risk for maintaining these systems and an opportunity for those who decide to invest in learning the language and the OTP ecosystem. In a market where reliability is worth more than novelty, Erlang remains a solid—and well-paid—bet.&lt;/p&gt;

</description>
      <category>erlang</category>
    </item>
    <item>
      <title>How Social Media Algorithms and Search Engines Work — and What That Means for Erlang’s Digital Space</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Tue, 27 Oct 2026 10:33:55 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/how-social-media-algorithms-and-search-engines-work-and-what-that-means-for-erlangs-digital-space-3ob3</link>
      <guid>https://dev.to/matheuscamarques/how-social-media-algorithms-and-search-engines-work-and-what-that-means-for-erlangs-digital-space-3ob3</guid>
      <description>&lt;h1&gt;
  
  
  How Social Media Algorithms and Search Engines Work — and What That Means for Erlang’s Digital Space
&lt;/h1&gt;

&lt;p&gt;The Erlang community produces extremely high-quality technical content: tutorials, articles, videos, and forum discussions. However, &lt;strong&gt;publishing is not the same as being distributed&lt;/strong&gt;. Between what a developer writes and what a new programmer actually finds, there is an invisible — and decisive — layer of ranking algorithms.&lt;/p&gt;

&lt;p&gt;Understanding how these systems work is not “marketing.” It is a &lt;strong&gt;distribution engineering skill&lt;/strong&gt; as important as knowing how to write a good &lt;code&gt;gen_server&lt;/code&gt;. This article explains the mechanics behind social media algorithms and search engines and shows how Erlang developers can use that knowledge to strengthen the language’s digital space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: How social media algorithms work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The universal principle: rank, don’t just publish
&lt;/h3&gt;

&lt;p&gt;Social media algorithms are AI-driven ranking systems that personalize each user’s feed based on engagement signals, relevance, and past behavior. The general flow is consistent across platforms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Collect eligible content&lt;/strong&gt; (posts the user has not yet seen);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluate ranking signals&lt;/strong&gt; (engagement, relevance, behavior);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predict value&lt;/strong&gt; for that specific user;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort the final feed&lt;/strong&gt; from most relevant to least relevant.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The question the algorithm asks is not “what is newest?” but rather “&lt;strong&gt;what is this specific person most likely to engage with right now?&lt;/strong&gt; ”&lt;/p&gt;

&lt;p&gt;At Meta (Facebook, Instagram, Threads), this process is made explicit in four stages: &lt;strong&gt;inventory&lt;/strong&gt; (gather potential posts), &lt;strong&gt;signals&lt;/strong&gt; (thousands of pieces of information about each post), &lt;strong&gt;predictions&lt;/strong&gt; (machine learning models estimate the probability of interaction), and &lt;strong&gt;value score&lt;/strong&gt; (which defines the final feed order).&lt;/p&gt;

&lt;h3&gt;
  
  
  The dev.to case: a community algorithm, not a vanity algorithm
&lt;/h3&gt;

&lt;p&gt;For those publishing Erlang content on dev.to, how it works is particularly revealing. Unlike vanity platforms, dev.to &lt;strong&gt;penalizes mass publishing without community participation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A controlled experiment with 30 articles published over two weeks resulted in &lt;strong&gt;zero reactions and about 60 total views&lt;/strong&gt; — an average of two views per article. The author’s diagnosis was precise: “I was broadcasting into the void and expecting an echo. dev.to is not a publishing platform; it is a community. Communities reward participation, not publishing.”&lt;/p&gt;

&lt;p&gt;What actually drives discovery on dev.to, according to empirical analysis of the platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Early reactions are decisive&lt;/strong&gt;: an article that receives 3–5 reactions in the first hour is boosted; an article with zero reactions in the first hour is buried.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comments are the strongest signal&lt;/strong&gt;: a single comment can trigger the algorithm to show the article to more people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tag choice is critical&lt;/strong&gt;: high-traffic tags (such as &lt;code&gt;#beginners&lt;/code&gt; or &lt;code&gt;#tutorial&lt;/code&gt;) work better than low-traffic niche tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The AI-assisted content label can hurt&lt;/strong&gt;: the dev.to algorithm appears to deprioritize content flagged as generated with AI assistance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition, dev.to appears to operate with an &lt;strong&gt;algorithmic budget per author&lt;/strong&gt;: empirically, only the first post of each day seems to be effectively surfaced by the algorithm, which makes consistency (one post per day) more effective than raw volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  The YouTube case: session satisfaction above all
&lt;/h3&gt;

&lt;p&gt;The YouTube algorithm has evolved to prioritize &lt;strong&gt;long-term viewer satisfaction&lt;/strong&gt; instead of short-term spikes. In 2026, the central metric stopped being raw watch time and became the &lt;strong&gt;contribution to the viewing session&lt;/strong&gt; — that is, how much your video keeps the viewer watching other videos on the platform.&lt;/p&gt;

&lt;p&gt;Three ranking signals dominate: &lt;strong&gt;engagement&lt;/strong&gt; (click-through rate and watch time), &lt;strong&gt;satisfaction&lt;/strong&gt; (likes, shares, and satisfaction surveys), and &lt;strong&gt;relevance&lt;/strong&gt; (keyword and topic matching).&lt;/p&gt;

&lt;p&gt;For Erlang content creators, two 2026 changes are especially important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Small channels can compete for reach&lt;/strong&gt;: the algorithm now tests videos with small audiences first. If initial engagement (watch time and click-through rate) is strong, the video is promoted to larger audiences through suggested feeds and Shorts. Subscriber count matters less than viewer response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old videos can gain new life&lt;/strong&gt;: if a topic becomes trending again, YouTube can resurface relevant content even if it was published years earlier. This favors the production of &lt;strong&gt;evergreen&lt;/strong&gt; content about fundamental Erlang and OTP concepts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;YouTube also introduced the &lt;strong&gt;“Hype”&lt;/strong&gt; feature for channels with 500 to 500,000 subscribers: fans can “hype” a new video, giving it a temporary ranking boost in the Explore feed. For the Erlang community, this means that &lt;strong&gt;mobilizing early supporters in the first 7 days after publication&lt;/strong&gt; can offset the lack of scale of a small channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: How search engines work (Google)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google does not follow a checklist — it trains models
&lt;/h3&gt;

&lt;p&gt;Google abandoned the logic of an “SEO checklist” years ago. The system uses &lt;strong&gt;helpful content guidelines&lt;/strong&gt; to train machine learning models with datasets evaluated by human Quality Raters. The systems learn to predict what is a genuinely useful, people-first result instead of applying manually coded rules.&lt;/p&gt;

&lt;p&gt;The 2026 helpful content updates made this even more explicit: content evaluated as “unhelpful” sends a &lt;strong&gt;negative signal&lt;/strong&gt; to the search algorithm.&lt;/p&gt;

&lt;h3&gt;
  
  
  The factors that actually matter in 2026
&lt;/h3&gt;

&lt;p&gt;The most important ranking factors are organized into five buckets: &lt;strong&gt;domain authority, topical authority, document quality, freshness, and engagement&lt;/strong&gt;. In practice, this translates into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High-quality content&lt;/strong&gt; as the number one factor;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relevant backlinks&lt;/strong&gt; (not just any link, but links from authoritative sources);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile optimization, page speed, and user experience&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alignment with search intent&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS and solid technical SEO&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The new “helpful content” guidelines: what Google began to require
&lt;/h3&gt;

&lt;p&gt;Google’s official documentation (Search Essentials) updated in 2026 emphasizes attributes that human evaluators began to check explicitly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human effort&lt;/strong&gt;: mass generation with little curation is devalued. The systems are designed to reward content that took work to make.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Originality&lt;/strong&gt;: unique perspectives, proprietary data, or tests that are not available in other results. “If an AI summary can completely rephrase your post, you are vulnerable.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill and experience&lt;/strong&gt;: technical topics need experts; communities value lived experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Factual accuracy&lt;/strong&gt;: explicit caution against unreviewed AI hallucinations. “Fluent prose does not replace accurate facts.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent authorship&lt;/strong&gt;: fake personas or fabricated biographies are low-quality signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Erlang content, this is a &lt;strong&gt;huge competitive advantage&lt;/strong&gt;. An article written by someone who actually debugged a &lt;code&gt;supervisor&lt;/code&gt; in production, with real code and a real-world account, has exactly the kind of “human effort” and “originality” that Google began to reward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical SEO in 2026: readability for AI beyond Googlebot
&lt;/h3&gt;

&lt;p&gt;The scope of technical SEO has expanded. It is not enough to be crawlable and indexable by Googlebot; content must be &lt;strong&gt;readable and interpretable by generative answer engines&lt;/strong&gt; such as ChatGPT, Perplexity, Gemini, and Google’s AI Overviews.&lt;/p&gt;

&lt;p&gt;The technical pillars include: &lt;strong&gt;crawlability, indexability, performance, rendering, security, architecture, and readability by AI machines&lt;/strong&gt;. For the latter, recommended practices are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure that HTML &lt;strong&gt;does not rely exclusively on JavaScript&lt;/strong&gt; for the main content;&lt;/li&gt;
&lt;li&gt;Optimize &lt;strong&gt;semantic content&lt;/strong&gt; (headings, lists, tables);&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;structured data&lt;/strong&gt; (Schema.org) to provide context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The discipline of &lt;strong&gt;GEO (Generative Engine Optimization)&lt;/strong&gt; emerged to optimize citation in AI answers, but the reality is that more than 80% of traditional SEO remains the foundation for appearing both in search engines and in answer engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: What this means for Erlang’s digital space
&lt;/h2&gt;

&lt;p&gt;Now that the mechanics are clear, the roadmap for the Erlang community becomes actionable.&lt;/p&gt;

&lt;h3&gt;
  
  
  For dev.to articles and blogs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Publish consistently daily, not in bursts.&lt;/strong&gt; The dev.to algorithm appears to prioritize only the first post of each day. Three articles per week distributed across different days work better than five articles on a single day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be present in the first hour.&lt;/strong&gt; Ask community colleagues to read and react in the first 60 minutes. A single comment can trigger algorithmic distribution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Participate before publishing.&lt;/strong&gt; Comment on other developers’ articles under the &lt;code&gt;#erlang&lt;/code&gt; and &lt;code&gt;#beam&lt;/code&gt; tags before promoting your own content. dev.to rewards community participation, not broadcasting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use broad discovery tags&lt;/strong&gt; (&lt;code&gt;#beginners&lt;/code&gt;, &lt;code&gt;#tutorial&lt;/code&gt;, &lt;code&gt;#programming&lt;/code&gt;) in combination with niche tags (&lt;code&gt;#erlang&lt;/code&gt;, &lt;code&gt;#otp&lt;/code&gt;). Exclusively niche tags limit initial reach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write from real experience.&lt;/strong&gt; Google is training models to reward effort, originality, and lived experience. A tutorial about “how I solved a &lt;code&gt;gen_server&lt;/code&gt; deadlock in production” has more algorithmic value than yet another generic syntax explanation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  For YouTube videos
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Focus on retention and session contribution&lt;/strong&gt;, not just views. The 2026 algorithm rewards videos that keep viewers watching related content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Take advantage of the small-audience testing phase.&lt;/strong&gt; YouTube tests new videos with smaller audiences first. Make sure the first viewers — Erlang community members — watch to the end and interact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobilize “Hype” in the first 7 days.&lt;/strong&gt; For channels between 500 and 500,000 subscribers, manual fan boosts can overcome initial retention barriers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Produce evergreen content about OTP fundamentals.&lt;/strong&gt; Concepts such as &lt;code&gt;supervisors&lt;/code&gt;, &lt;code&gt;gen_statem&lt;/code&gt;, and fault tolerance remain relevant for years and can be resurfaced by the algorithm when the topic trends again.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  For search engine presence
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Publish articles in semantic HTML with structured data.&lt;/strong&gt; If your Erlang tutorial is on a personal blog, ensure the main content does not depend on JavaScript and use Schema.org markup (&lt;code&gt;Article&lt;/code&gt;, &lt;code&gt;TechArticle&lt;/code&gt;). This helps both Googlebot and generative answer engines interpret and cite your content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build topical authority, not just domain authority.&lt;/strong&gt; A small blog that consistently publishes about Erlang, OTP, and BEAM can develop topical authority in Google’s eyes, competing with larger sites for specific queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid “commodity” content.&lt;/strong&gt; Generic tutorials that an AI could generate from existing documentation are vulnerable. The differentiator is &lt;strong&gt;proprietary data, benchmarks, case studies, and real production experience&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion: the Erlang community has an algorithmic advantage
&lt;/h2&gt;

&lt;p&gt;The 2026 algorithms — both social media and search engines — converge on the same principle: &lt;strong&gt;reward genuine human effort, real experience, and community contribution&lt;/strong&gt;, instead of raw volume or optimization tricks.&lt;/p&gt;

&lt;p&gt;This is excellent news for the Erlang community. Erlang developers work with systems that require deep understanding of concurrency, fault tolerance, and distributed system design. That kind of knowledge &lt;strong&gt;is not a commodity&lt;/strong&gt;. An article or video that captures that real experience has exactly the profile that modern algorithms were trained to value.&lt;/p&gt;

&lt;p&gt;Erlang’s digital space will not be built by those who publish faster or in greater quantity. It will be built by those who understand that &lt;strong&gt;distribution is part of engineering&lt;/strong&gt; — and that the best way to hack the algorithm is, in the end, to produce content that is genuinely worth distributing.&lt;/p&gt;

</description>
      <category>erlang</category>
    </item>
    <item>
      <title>How Erlang Developers Can Build a Vibrant Digital Space for Erlang</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Tue, 27 Oct 2026 10:11:15 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/how-erlang-developers-can-build-a-vibrant-digital-space-for-erlang-3hk</link>
      <guid>https://dev.to/matheuscamarques/how-erlang-developers-can-build-a-vibrant-digital-space-for-erlang-3hk</guid>
      <description>&lt;h1&gt;
  
  
  How Erlang Developers Can Build a Vibrant Digital Space for Erlang
&lt;/h1&gt;

&lt;p&gt;The Erlang community is recognized for its technical resilience, but the same cannot always be said for its digital presence. While the Elixir ecosystem has built a formidable content machine, Erlang — the language that started it all — often remains in the shadow, with solid documentation but little continuous production of accessible, up-to-date content.&lt;/p&gt;

&lt;p&gt;This article proposes a practical roadmap for Erlang developers who want to change this scenario. The idea is simple: &lt;strong&gt;turn technical knowledge into digital presence&lt;/strong&gt; through three complementary fronts — weekly articles, YouTube videos, and active research on how to contribute better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why care about Erlang’s “digital space”?
&lt;/h2&gt;

&lt;p&gt;The Erlang Ecosystem Foundation (EEF) exists precisely to “expand awareness of the Erlang Ecosystem and participation in its community.” This is a stated goal, and individual developers can be direct agents in this process. The EEF’s Marketing Working Group, for example, publishes interviews and use-case articles to keep the community engaged.&lt;/p&gt;

&lt;p&gt;Moreover, content production is not just “marketing.” When a developer writes a tutorial or records a video, they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documenting the ecosystem&lt;/strong&gt; in a living, contextualized way;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowering the barrier to entry&lt;/strong&gt; for new programmers;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creating a historical record&lt;/strong&gt; of practices and solutions that would otherwise be lost in forum threads or chat channels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pillar 1: Weekly articles — 3 posts per week
&lt;/h2&gt;

&lt;p&gt;The goal of three articles per week may seem ambitious, but it is feasible if there is a rotation of formats and careful topic curation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Formats that work
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Step-by-step tutorials&lt;/strong&gt;: from the classic &lt;code&gt;gen_server&lt;/code&gt; to more specific topics such as &lt;code&gt;supervisors&lt;/code&gt;, &lt;code&gt;OTP behaviours&lt;/code&gt;, or integration with ecosystem libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Curiosities and language “behind the scenes”&lt;/strong&gt;: Why is Erlang’s pattern matching different? How does the VM manage lightweight processes? What are &lt;code&gt;EEPs&lt;/code&gt; (Erlang Enhancement Proposals), and how do they shape the language?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Short case studies&lt;/strong&gt;: how a specific feature solved a real concurrency or fault-tolerance problem.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Where to publish and how to distribute
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;dev.to&lt;/strong&gt; is an excellent starting point: the publishing barrier is low, the audience is technical, and the platform allows the use of tags such as &lt;code&gt;#erlang&lt;/code&gt;, &lt;code&gt;#beam&lt;/code&gt;, and &lt;code&gt;#otp&lt;/code&gt;. In addition, the official Erlang forums have dedicated &lt;strong&gt;Blog Posts&lt;/strong&gt; sections where content can be shared in an organized way — including keeping a single topic for all posts from your blog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Planet Erlang&lt;/strong&gt; is a meta-blog that aggregates posts from various developers and contributors, functioning as a natural showcase for those who publish regularly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pillar 2: YouTube videos
&lt;/h2&gt;

&lt;p&gt;YouTube is the platform where most new developers look for introductory content. There are already notable individual initiatives, such as the “Erlang bits” series, created by a community member with the goal of maintaining a cadence of &lt;strong&gt;one video per week&lt;/strong&gt; on topics such as &lt;code&gt;gen_server&lt;/code&gt; and basic language concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strategies to avoid getting stuck in production
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short, focused videos&lt;/strong&gt;: instead of trying to cover “all of Erlang” in one hour, record 5- to 10-minute videos about a single concept or a single function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screen recording with narration&lt;/strong&gt;: screencasts are easier to produce than videos with elaborate scripts and a camera. The aforementioned “Erlang bits” follows exactly this model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuse material from articles&lt;/strong&gt;: a written tutorial can become a video, and a video can generate three short posts with the main excerpts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Channels such as the &lt;strong&gt;BEAM Channel&lt;/strong&gt; already show that there is room for continuous content about the BEAM VM, publishing videos “several times per week” about how different aspects of the ecosystem work. The difference is that most of this content is aimed at Elixir; there is a clear gap for content &lt;strong&gt;centered on Erlang&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pillar 3: Research on how to contribute to the digital space
&lt;/h2&gt;

&lt;p&gt;Publishing without researching is building in the dark. The Erlang community has a culture of &lt;strong&gt;polls and surveys&lt;/strong&gt; that can be used as a compass for content production.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to research (and how)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quick forum polls&lt;/strong&gt;: the Erlang Forums have an active “Polls” section with topics such as “Which shell do you use?”, “Which webserver do you use?”, and “Which operating system do you develop on?” These data points indicate what the community is using &lt;em&gt;now&lt;/em&gt; and, therefore, what deserves up-to-date tutorials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Larger surveys&lt;/strong&gt;: occasionally, initiatives such as “State of BEAM” or surveys focused on diversity and inclusion appear, helping to understand who the community is and what barriers exist for newcomers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gap analysis&lt;/strong&gt;: compare the amount of Erlang vs. Elixir content on dev.to, YouTube, and blogs. Where there is less content, there is more opportunity for meaningful contribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to turn research into content
&lt;/h3&gt;

&lt;p&gt;If a poll reveals that a significant portion of the community uses a specific tool, that is a signal to create a tutorial about it. If a survey shows that beginners struggle with a concept (for example, OTP supervisors), that is the topic for the next video. Research stops being an academic exercise and becomes an &lt;strong&gt;editorial calendar&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The role of the EEF and Working Groups
&lt;/h2&gt;

&lt;p&gt;The Erlang Ecosystem Foundation offers a structure that can amplify individual efforts. The foundation maintains &lt;strong&gt;Working Groups&lt;/strong&gt; focused on areas such as &lt;strong&gt;Education, Training, and Adoption&lt;/strong&gt; and &lt;strong&gt;Marketing&lt;/strong&gt;, whose mandate is precisely to “facilitate, evolve, and consolidate educational materials” and “expand awareness.”&lt;/p&gt;

&lt;p&gt;In addition, the EEF offers &lt;strong&gt;stipends&lt;/strong&gt; for projects that benefit the BEAM community, with a focus on “online workshops, training materials, hands-on training, and open-source development work.” A developer who wants to produce a tutorial series or a video course can apply for this financial support to make dedication to the project viable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical roadmap to start this week
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose a topic for your first article&lt;/strong&gt; and write a 300-word outline. Publish it on dev.to with the tags &lt;code&gt;#erlang&lt;/code&gt; and &lt;code&gt;#beam&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record a 5-minute screencast&lt;/strong&gt; on the same topic. Do not worry about sophisticated editing; focus on clean audio and visible code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open a poll on the Erlang Forums&lt;/strong&gt; asking the community which topic they would like to see covered next. Use the result as the agenda for the following week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join the EEF Slack&lt;/strong&gt; (the invite is available at &lt;code&gt;erlef.org/slack-invite/erlanger&lt;/code&gt;) and participate in discussions in the &lt;strong&gt;Education Working Group&lt;/strong&gt; or the &lt;strong&gt;Marketing Working Group&lt;/strong&gt; to align your effort with larger initiatives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Erlang’s digital space will not be built by one person or one company. It will be built by developers who decide that &lt;strong&gt;three articles per week, short videos, and continuous research&lt;/strong&gt; are a legitimate way to contribute to the language that powers critical systems around the world. The infrastructure already exists — forums, foundation, working groups, publishing platforms. What is missing is the content. And that content can start with you.&lt;/p&gt;

</description>
      <category>erlang</category>
    </item>
    <item>
      <title>Três anos transformando o inglês em uma ponte para o mundo: minha jornada na King's English</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Sun, 25 Oct 2026 23:15:17 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/tres-anos-transformando-o-ingles-em-uma-ponte-para-o-mundo-minha-jornada-na-kings-english-42oa</link>
      <guid>https://dev.to/matheuscamarques/tres-anos-transformando-o-ingles-em-uma-ponte-para-o-mundo-minha-jornada-na-kings-english-42oa</guid>
      <description>&lt;p&gt;&lt;strong&gt;Três anos transformando o inglês em uma ponte para o mundo: minha jornada na King's English&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Há três anos, eu mal conseguia formar frases completas sem sentir aquele bloqueio típico de quem traduz mentalmente do português para o inglês. A virada veio quando iniciei meus estudos na &lt;strong&gt;King's English&lt;/strong&gt;, a escola do professor Adrian King. Hoje, consigo me comunicar com naturalidade em viagens, reuniões de trabalho e conversas casuais com pessoas de diferentes nacionalidades — e olha que a estrada até aqui foi construída com método, constância e muita prática real.&lt;/p&gt;

&lt;h3&gt;
  
  
  O começo: por que escolhi a King's English
&lt;/h3&gt;

&lt;p&gt;O que me chamou atenção no site da escola (adriankingenglish.com.br) foi a transparência: &lt;strong&gt;30 anos de experiência&lt;/strong&gt;, mais de &lt;strong&gt;5.000 alunos formados&lt;/strong&gt; e uma metodologia que não promete milagres, mas entrega resultados mensuráveis. Diferente de cursos genéricos, a proposta era 100% personalizada, com um plano de estudo desenhado para os meus objetivos — na época, eu queria destravar a conversação para viagens e, depois, para o ambiente profissional.&lt;/p&gt;

&lt;h3&gt;
  
  
  A metodologia que fez diferença
&lt;/h3&gt;

&lt;p&gt;A base do trabalho é científica, mas a aplicação é surpreendentemente prática. A escola utiliza a &lt;strong&gt;Abordagem Comunicativa&lt;/strong&gt;, com foco em conversação com propósito, e a &lt;strong&gt;Aprendizagem Baseada em Tarefas (TBLT)&lt;/strong&gt;, que simula situações reais do dia a dia. Isso significa que, desde as primeiras aulas, eu não estava apenas preenchendo lacunas gramaticais: eu estava resolvendo problemas, negociando ideias e me expressando — exatamente como acontece no mundo real.&lt;/p&gt;

&lt;p&gt;Outro ponto decisivo foi a &lt;strong&gt;personalização&lt;/strong&gt;. Meu plano incluía sessões de &lt;em&gt;feedback&lt;/em&gt; constante, correções em tempo real e ajustes de rota conforme minha evolução. Estudos citados pela própria escola mostram que abordagens centradas no aluno aumentam motivação, engajamento e retenção. No meu caso, isso se traduziu em uma curva de aprendizado muito mais íngreme do que qualquer curso em grupo que eu já havia tentado.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minha evolução em três anos
&lt;/h3&gt;

&lt;p&gt;No primeiro ano, o foco foi destravar o medo de errar. As aulas semanais de 60 minutos, com material exclusivo e acompanhamento de progresso, me deram uma estrutura sólida para sair do nível intermediário “travado” para uma comunicação funcional em situações previsíveis.&lt;/p&gt;

&lt;p&gt;No segundo ano, a virada foi qualitativa. Passei a participar de conversas mais longas, com vocabulário específico para negócios e viagens. A correção gentil e contínua — aquela que a gente nem percebe na hora, mas que ajusta a estrutura da frase — foi fundamental para ganhar confiança e fluidez.&lt;/p&gt;

&lt;p&gt;Hoje, no terceiro ano, a comunicação com o mundo todo é uma realidade. Consigo participar de reuniões em inglês, fazer &lt;em&gt;small talk&lt;/em&gt; sem ansiedade e me virar em aeroportos, hotéis e restaurantes com naturalidade. O inglês deixou de ser uma matéria de estudo e passou a ser uma ferramenta de trabalho e de vida.&lt;/p&gt;

&lt;h3&gt;
  
  
  O que dizem outros alunos: relatos que confirmam a experiência
&lt;/h3&gt;

&lt;p&gt;Pesquisei avaliações de outros estudantes e encontrei padrões que se repetem. Em plataformas como o Meetup, onde o professor Adrian conduz grupos de conversação, os depoimentos destacam a atmosfera acolhedora e a correção eficaz:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Adrian é um anfitrião excepcional! Ele cria uma atmosfera calorosa, amigável e acolhedora onde todos se sentem confortáveis, independentemente do nível de inglês.”&lt;/em&gt; — Laure, membro do grupo&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Ele não apenas conversa com você, mas também corrige gentilmente seus erros sem que você perceba. Ele me ajudou muito a expandir meu vocabulário e a aprender a construir frases mais longas.”&lt;/em&gt; — Gizem&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Ele é muito atencioso durante a aula e dá feedback valioso sobre seus erros. Você sente que está em uma conversa amigável e em uma aula de verdade ao mesmo tempo.”&lt;/em&gt; — Bora&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Esses relatos ecoam exatamente o que vivi: a combinação de um ambiente seguro para errar com a correção técnica precisa de um professor experiente. Não é à toa que o professor acumula mais de &lt;strong&gt;7.900 aulas ministradas&lt;/strong&gt; e uma avaliação de &lt;strong&gt;4,8 estrelas&lt;/strong&gt; em plataformas de ensino.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusão: valeu cada minuto
&lt;/h3&gt;

&lt;p&gt;Três anos podem parecer muito tempo, mas, quando o método é certo, o tempo trabalha a seu favor. A King's English não me vendeu fluência instantânea; ofereceu um caminho estruturado, personalizado e baseado em evidências. Hoje, quando converso com alguém em inglês — seja em uma videochamada com um colega estrangeiro ou em uma viagem —, percebo o quanto aquele investimento inicial se transformou em autonomia.&lt;/p&gt;

&lt;p&gt;Se você está pensando em começar (ou recomeçar) o inglês, meu conselho é: procure um método que te coloque para falar desde o primeiro dia, com correção inteligente e objetivos claros. Foi exatamente isso que a King's English fez por mim — e é por isso que recomendo de olhos fechados.&lt;/p&gt;

&lt;p&gt;Conheça a &lt;a href="https://adriankingenglish.com.br/" rel="noopener noreferrer"&gt;King's English&lt;/a&gt;&lt;/p&gt;

</description>
      <category>english</category>
      <category>ingles</category>
      <category>language</category>
      <category>elixir</category>
    </item>
    <item>
      <title>A menina que atravessou a cidade</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Tue, 20 Oct 2026 03:45:27 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/a-menina-que-atravessou-a-cidade-1mj7</link>
      <guid>https://dev.to/matheuscamarques/a-menina-que-atravessou-a-cidade-1mj7</guid>
      <description>&lt;p&gt;&lt;strong&gt;A menina que atravessou a cidade&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enedina nasceu em Curitiba, em 13 de janeiro de 1913. Filha de Paulo Marques e Virgínia Alves Marques, veio ao mundo num Brasil que tinha abolido a escravidão havia apenas 25 anos. A mãe trabalhava como doméstica; o pai fazia pequenos serviços. A casa era pobre. Quando os pais se separaram, a mãe foi trabalhar na casa do major e delegado Domingos Nascimento Sobrinho.&lt;/p&gt;

&lt;p&gt;Enedina cresceu perto dessa família, mas não como filha adotiva. Cresceu como &lt;strong&gt;filha da empregada&lt;/strong&gt;. Ainda assim, como tinha idade próxima da filha do major, estudou na mesma escola particular que ela. Foi ali que aprendeu uma lição que carregaria para sempre: conhecer o mundo dos ricos não é o mesmo que pertencer a ele.&lt;/p&gt;

&lt;p&gt;Depois veio o curso normal, a formação de professora. Enedina dava aulas. Mais tarde foi morar com a família do construtor Mathias Caron, ajudando nos serviços domésticos e cuidando da filha do casal em troca de moradia. Foi nesse ambiente que viu de perto a construção civil — canteiros, plantas, obras — e decidiu que queria engenharia. Não era um caminho para menina pobre, muito menos para mulher negra. Mas ela fez o cursinho, pagou como pôde e entrou na Universidade Federal do Paraná.&lt;/p&gt;

&lt;p&gt;Na turma, era a única mulher e a única pessoa negra. Enfrentou perseguição de professores, reprovações e obstáculos que não eram técnicos: eram racismo e sexismo. Levou seis anos para concluir um curso de cinco. Formou-se em 1945. Tornou-se &lt;strong&gt;a primeira engenheira civil negra do Brasil&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Depois, trabalhou no serviço público, na Secretaria de Viação e Obras. Passou por obras como a Usina Capivari-Cachoeira. Numa obra, precisou impor respeito num ambiente feito para homens. Não fundou construtora, não administrou contratos privados, não herdou terras nem empresas. Fez carreira pública.&lt;/p&gt;

&lt;p&gt;E é aqui que a história responde à sua pergunta: &lt;strong&gt;por que ela não ficou rica?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Porque, naquele Brasil, riqueza vinha de herança, propriedade, empresa e redes familiares. Enedina tinha competência, não capital. A proximidade com famílias ricas lhe deu escola, contato e visão de mundo — não lhe deu herança. O racismo e o sexismo cobraram pedágio em cada etapa. E o salário público, mesmo bom, não constrói fortuna de elite.&lt;/p&gt;

&lt;p&gt;Ela ascendeu. Teve apartamento, viajou, aposentou-se em 1962 com proventos equiparados aos de um juiz, em reconhecimento aos serviços prestados. Morreu em 1981, com dignidade profissional. Mas não morreu rica no sentido de grande patrimônio. Morreu respeitada.&lt;/p&gt;

&lt;p&gt;Para a família, a história não é a de quem enriqueceu. É a de quem atravessou. Se você descende dos irmãos dela, você carrega essa memória por linha colateral: a menina que estudou ao lado dos filhos da elite sem pertencer à elite, e que mesmo assim abriu uma porta que estava fechada para todo mundo que se parecia com ela.&lt;/p&gt;

</description>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>How to Create an Easier Recommendation System with Elixir</title>
      <dc:creator>Matheus de Camargo Marques</dc:creator>
      <pubDate>Fri, 16 Oct 2026 11:30:27 +0000</pubDate>
      <link>https://dev.to/matheuscamarques/how-to-create-an-easier-recommendation-system-with-elixir-4p0o</link>
      <guid>https://dev.to/matheuscamarques/how-to-create-an-easier-recommendation-system-with-elixir-4p0o</guid>
      <description>&lt;h1&gt;
  
  
  How to Create an Easier Recommendation System with Elixir
&lt;/h1&gt;

&lt;p&gt;Recommendation systems power some of the most engaging experiences on the web—from Netflix suggesting your next binge to Amazon nudging you toward a purchase. While Python and its ML ecosystem often dominate this space, Elixir offers a compelling alternative: a functional, concurrent, fault-tolerant runtime that can handle real-time recommendations with elegance and scale.&lt;/p&gt;

&lt;p&gt;In this article, we'll build a content-based recommendation system from scratch in Elixir, starting with a clean, efficient implementation of cosine similarity. Then we'll explore how to extend it with Elixir's growing ML ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Elixir for Recommendation Systems?
&lt;/h2&gt;

&lt;p&gt;Elixir's strengths align surprisingly well with recommendation workloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt;: BEAM processes make it easy to parallelize similarity calculations across millions of user-item pairs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault tolerance&lt;/strong&gt;: Supervision trees ensure your recommendation service stays up even when individual computations fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional pipelines&lt;/strong&gt;: Data transformations (filter, map, reduce) map naturally to recommendation logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time capability&lt;/strong&gt;: Phoenix Channels enable live, personalized recommendations without polling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data locality&lt;/strong&gt;: ETS provides fast in-memory lookups and atomic operations with concurrent read access, ideal for storing and querying large item catalogs without leaving the BEAM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Elixir ML ecosystem is also maturing rapidly, with libraries like Nx, Axon, Scholar, and MeliGraph bringing production-grade ML capabilities to the language.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Algorithm: Cosine Similarity
&lt;/h2&gt;

&lt;p&gt;At the heart of most content-based recommenders is a simple question: &lt;em&gt;How similar is this item to what the user already likes?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cosine similarity answers this by measuring the angle between two vectors. Unlike Euclidean distance, it's scale-invariant—a user who rates everything highly isn't penalized compared to a more conservative rater. Cosine similarity is particularly convenient when items (and often users, categories, etc.) are represented in a shared latent space, and it has become a standard choice for k-NN based recommenders.&lt;/p&gt;

&lt;p&gt;Here's a clean, optimized implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Classification&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;alias&lt;/span&gt; &lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Database&lt;/span&gt;

  &lt;span class="nv"&gt;@movies&lt;/span&gt; &lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nv"&gt;@user&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Mattew San"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Public API ----------&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="nv"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movies&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="nv"&gt;@movies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_n&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;to_tuple&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;movies&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;to_tuple&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="c1"&gt;# score is cos² here — take sqrt only for the final display&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;:math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Extractors (2-tuple: {name, keyword_list}) ----------&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;      &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;to_tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:action&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:comedy&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:romance&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:horror&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="no"&gt;Keyword&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetch!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sci_fi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;a1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;a2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;a1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;c2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;e2&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Squared magnitude — no sqrt&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Returns cos²(v1, v2) — no sqrt at all&lt;/span&gt;
  &lt;span class="k"&gt;defp&lt;/span&gt; &lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;magnitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Implementation Is Smart
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Fixed-size tuples beat keyword lists for hot paths.&lt;/strong&gt;&lt;br&gt;
Converting preferences to a 5-element tuple eliminates the overhead of keyword list traversal during every similarity calculation. This is a classic Elixir optimization: encode data in a shape that matches how you'll use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. cos² avoids unnecessary square roots.&lt;/strong&gt;&lt;br&gt;
The standard cosine formula is &lt;code&gt;dot / (sqrt(mag1) * sqrt(mag2))&lt;/code&gt;. But if we're only using the score for &lt;em&gt;ranking&lt;/em&gt;, we can compare squared values directly. The square root is monotonic, so the ordering is identical. We only take &lt;code&gt;:math.sqrt/1&lt;/code&gt; once per result at the end for a human-friendly display value. This saves two &lt;code&gt;:math.sqrt/1&lt;/code&gt; calls per movie per recommendation request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Guard clauses prevent division by zero.&lt;/strong&gt;&lt;br&gt;
A zero-magnitude vector (a user with no preferences, or a movie with no genre ratings) would crash the calculation. The early return handles this gracefully.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Data Layer: A Movie Database Module
&lt;/h2&gt;

&lt;p&gt;To make the recommender runnable end-to-end, we need a catalog of items. Here's a compact but representative dataset that covers the major genre axes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Database&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nv"&gt;@movies&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------- Sci-Fi / Action ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Matrix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Interstellar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Inception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Blade Runner 2049"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Arrival"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Dune"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Dune: Part Two"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Action / Thriller ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Die Hard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Mad Max: Fury Road"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"John Wick"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Dark Knight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Comedy ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Grand Budapest Hotel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Superbad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Groundhog Day"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Romance / Drama ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Titanic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Notebook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"La La Land"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Eternal Sunshine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Horror ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Exorcist"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Hereditary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Get Out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Animation / Family ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Toy Story"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"WALL-E"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- War / Historical ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Saving Private Ryan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Dunkirk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Crime / Mafia ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Godfather"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Goodfellas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Fantasy / Adventure ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Lord of the Rings: Fellowship"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Princess Bride"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Superhero ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Iron Man"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Avengers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Spider-Man: No Way Home"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------- Cult / Mind-bending ----------&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Memento"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Prestige"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Parasite"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;@movies&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each movie is represented as a 5-dimensional vector across the genre axes: action, comedy, romance, horror, and sci_fi. This compact representation makes similarity computation fast and the dataset easy to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the System
&lt;/h2&gt;

&lt;p&gt;With both modules saved, you can run everything from IEx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Dune: Part Two"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9943&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Dune"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9921&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Interstellar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9812&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Blade Runner 2049"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9754&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Arrival"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9688&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Inception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9601&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Matrix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9547&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Ex Machina"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9322&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"The Martian"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9201&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Guardians of the Galaxy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.9188&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes total sense for the user &lt;code&gt;Mattew San&lt;/code&gt;, who has &lt;code&gt;action: 0.9&lt;/code&gt;, &lt;code&gt;comedy: 0.3&lt;/code&gt;, &lt;code&gt;romance: 0.1&lt;/code&gt;, &lt;code&gt;horror: 0.0&lt;/code&gt;, &lt;code&gt;sci_fi: 0.8&lt;/code&gt;—a clearly sci-fi/action profile with low tolerance for horror.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Different Profiles
&lt;/h3&gt;

&lt;p&gt;One of the best things about a compact dataset is that it covers the extremes well. Validate the behavior with contrasting profiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Romantic cinephile profile&lt;/span&gt;
&lt;span class="n"&gt;romantic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Ana"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;romantic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Titanic, The Notebook, Pride and Prejudice, La La Land, Before Sunrise&lt;/span&gt;

&lt;span class="c1"&gt;# Horror fan profile&lt;/span&gt;
&lt;span class="n"&gt;horror_fan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Bruno"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horror_fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; The Exorcist, Hereditary, The Conjuring, The Witch, It Follows&lt;/span&gt;

&lt;span class="c1"&gt;# Light comedy profile&lt;/span&gt;
&lt;span class="n"&gt;comedy_fan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Carla"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;action:&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;comedy:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;romance:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;horror:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;sci_fi:&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comedy_fan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;Classification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# =&amp;gt; Superbad, The Big Lebowski, The Grand Budapest Hotel, Shrek, Hot Fuzz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any of these return something odd (like a horror film at the top for the romantic profile), it signals that some weight in the dataset is misaligned—which is great, because it gives you immediate feedback on data quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking It Further: Libraries and Ecosystem Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;similarity&lt;/code&gt; Hex Package
&lt;/h3&gt;

&lt;p&gt;If you'd rather not maintain your own implementation, the &lt;code&gt;similarity&lt;/code&gt; library provides battle-tested cosine similarity, Simhash, and Sørensen–Dice calculations. It supports streaming comparisons and handles mismatched attribute sets—elements added don't have to have the exact same attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Similarity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Cosine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Similarity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Cosine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user_a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"comedy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Similarity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Cosine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user_b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"comedy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Similarity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Cosine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user_a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user_b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library also offers &lt;code&gt;cosine_srol/2&lt;/code&gt;, which multiplies cosine similarity by the square root of vector length—useful when comparing vectors of different lengths, as it rewards items with more overlapping attributes. The library requires Elixir 1.14 or later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graph-Based Recommendations with MeliGraph
&lt;/h3&gt;

&lt;p&gt;For collaborative filtering or "users who liked X also liked Y" scenarios, &lt;strong&gt;MeliGraph&lt;/strong&gt; brings Twitter-inspired graph algorithms to Elixir. Inspired by Twitter's WTF (Who to Follow) and GraphJet systems, and by Oban's OTP patterns, MeliGraph provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personalized PageRank&lt;/strong&gt; via Monte Carlo random walks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SALSA&lt;/strong&gt; for bipartite graphs (hubs/authorities)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SimilarItems&lt;/strong&gt; with 2-hop co-occurrence and Jaccard/Cosine normalization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GlobalRank&lt;/strong&gt; for cold-start ranking by in-degree&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LightGCN&lt;/strong&gt; embeddings trained with Nx.Defn + BPR loss, with automatic fallback to SALSA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MeliGraph is single-writer/multi-reader via GenServer + ETS, supports distributed mode with Horde (opt-in), and is telemetry-first. It requires EXLA for optimal LightGCN performance and uses Nx as a mandatory dependency.&lt;/p&gt;

&lt;p&gt;Here's a quick start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Start an instance&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MeliGraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;name:&lt;/span&gt; &lt;span class="ss"&gt;:my_graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;graph_type:&lt;/span&gt; &lt;span class="ss"&gt;:bipartite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;testing:&lt;/span&gt; &lt;span class="ss"&gt;:sync&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Insert edges (user → content)&lt;/span&gt;
&lt;span class="no"&gt;MeliGraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user:1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"post:a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:like&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;MeliGraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user:2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"post:a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:like&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;MeliGraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user:2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"post:b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:like&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Get recommendations&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MeliGraph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:my_graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user:1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;algorithm:&lt;/span&gt; &lt;span class="ss"&gt;:salsa&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;seed_size:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;top_k:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Numerical Computing with Nx and Scholar
&lt;/h3&gt;

&lt;p&gt;For larger-scale scenarios, &lt;strong&gt;Nx&lt;/strong&gt; (Numerical Elixir) brings tensor operations and GPU acceleration to the language. Cosine similarity in Nx looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;defmodule&lt;/span&gt; &lt;span class="no"&gt;CosSim&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Defn&lt;/span&gt;

  &lt;span class="n"&gt;defn&lt;/span&gt; &lt;span class="n"&gt;cosine_similarity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Elixir community recommends using &lt;strong&gt;Scholar&lt;/strong&gt;'s &lt;code&gt;CosineDistance&lt;/code&gt; and subtracting from 1 for similarity. Scholar also provides a &lt;code&gt;BruteKNN&lt;/code&gt; module for brute-force k-nearest neighbor search, which accepts &lt;code&gt;:cosine&lt;/code&gt; as a distance metric:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Scholar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Neighbors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BruteKNN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;num_neighbors:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tensor&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;neighbors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Scholar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;Neighbors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="no"&gt;BruteKNN&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With EXLA as the backend, these operations can run on GPU. Scholar is still in pre-release, so you may need to pull it manually from GitHub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vector Databases for Scale
&lt;/h3&gt;

&lt;p&gt;For production-scale similarity search over embeddings, dedicated vector database libraries are emerging. The &lt;code&gt;vector-db-elixir&lt;/code&gt; project enables fast exact k-NN search over stored vectors with configurable distance metrics (cosine, L2, dot product), eliminating manual matching and enabling real-time recommendations. Similarly, &lt;code&gt;hnswlib&lt;/code&gt; provides an Elixir binding for the HNSW library with cosine similarity support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Performance at Scale
&lt;/h3&gt;

&lt;p&gt;The Elixir community has learned hard lessons about scaling similarity calculations. At around 5 million records, loading all data into memory becomes impractical—pushing computation to the database is recommended when possible. For intermediate scales, &lt;code&gt;Flow&lt;/code&gt; or &lt;code&gt;GenStage&lt;/code&gt; can parallelize the work across cores. For very large lists (over 10k items), consider using Matrex for dot products or leveraging &lt;code&gt;pgvector&lt;/code&gt; in PostgreSQL for vector similarity search.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Quality Observations
&lt;/h3&gt;

&lt;p&gt;Two things stand out in practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Genre dominance.&lt;/strong&gt; If one genre axis (like &lt;code&gt;sci_fi&lt;/code&gt;) has high values across many items, any user with a high preference for that genre will see almost exclusively those items at the top. To encourage diversity, consider normalizing each dimension (dividing each feature by its dataset mean) before computing cosine similarity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Near-identical vectors.&lt;/strong&gt; Films like &lt;code&gt;The Godfather&lt;/code&gt; and &lt;code&gt;The Godfather Part II&lt;/code&gt; may have identical weights, making them indistinguishable. Adding a secondary feature (release year, director, popularity) as a tie-breaker resolves this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Diversity to Results
&lt;/h3&gt;

&lt;p&gt;If you want to avoid monothematic lists, apply a simple diversity filter—for example, at most 2 films per dominant genre:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight elixir"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="n"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movies&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="nv"&gt;@movies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_n&lt;/span&gt; &lt;span class="p"&gt;\\&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;user_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;to_tuple&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;movies&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="ss"&gt;:math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;prefs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;to_tuple&lt;/span&gt;&lt;span class="p"&gt;()))}&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;({[],&lt;/span&gt; &lt;span class="p"&gt;%{}},&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;genre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dominant_genre&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="p"&gt;{[{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="no"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;genre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;elem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the list varied without sacrificing much relevance.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Roadmap
&lt;/h2&gt;

&lt;p&gt;Here's how to evolve your simple recommender into a production system:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prototype&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Content-based filtering with cosine similarity&lt;/td&gt;
&lt;td&gt;Your &lt;code&gt;Classification&lt;/code&gt; module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Small scale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Add collaborative filtering via co-occurrence&lt;/td&gt;
&lt;td&gt;MeliGraph &lt;code&gt;SimilarItems&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Medium scale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Matrix factorization for latent features&lt;/td&gt;
&lt;td&gt;Matrex SVD, Nx tensors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Large scale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Neural collaborative filtering&lt;/td&gt;
&lt;td&gt;Nx + Axon, Scholar metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Production&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Real-time serving, A/B testing&lt;/td&gt;
&lt;td&gt;Phoenix, Oban, FLAME&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Elixir offers a uniquely pragmatic path to building recommendation systems. You can start with a 60-line module that runs in any IEx session, then scale up to graph algorithms, tensor operations, and GPU-accelerated neural networks—all without leaving the language.&lt;/p&gt;

&lt;p&gt;The key insight is that recommendation systems are fundamentally about &lt;strong&gt;data transformation&lt;/strong&gt;: user preferences → similarity scores → ranked items. That's a pipeline, and pipelines are what Elixir does best.&lt;/p&gt;

&lt;p&gt;Whether you're building a movie recommender like the one above, a product suggestion engine for e-commerce, or a content feed for a social platform, the functional, concurrent, and fault-tolerant nature of Elixir gives you a solid foundation to start simple—and the ecosystem to grow sophisticated when you need to.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>programming</category>
      <category>productivity</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
