<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
   <channel>
      <title>Aiono Blog</title>
      <link>https://blog.aiono.dev/</link>
      <description>A RSS news feed containing my blog posts about programming.</description>
      <language>en-us</language>
      <pubDate>14 Mar 2026 00:00:00 GMT</pubDate>
      <docs>https://www.rssboard.org/rss-specification</docs>
      <generator>Aiono's Custom Static Site Generator</generator>
      <managingEditor>sahinonur2000@hotmail.com (Onur Sahin)</managingEditor>
      <webMaster>sahinonur2000@hotmail.com (Onur Sahin)</webMaster>
      <atom:link href="https://blog.aiono.dev/feed.xml" rel="self" type="application/rss+xml"/>      
      
      <item>
         <title>Can LLMs SAT?</title>
         <link>https://blog.aiono.dev/posts/can-llms-sat.html</link>
         <guid>https://blog.aiono.dev/posts/can-llms-sat.html</guid>
         <pubDate>24 Feb 2026 00:00:00 GMT</pubDate>
         <description><![CDATA[<p>In recent years, LLMs have shown significant improvements in their overall performance. When they first became mainstream a couple of years before, they were already impressive with their seemingly human-like conversation abilities, but their reasoning always lacked. They were able to describe any sorting algorithm in the style of your favorite author; on the other hand, they weren't able to consistently perform addition. However, they improved significantly, and it's more and more difficult to find examples where they fail to reason. This created the belief that with enough scaling, LLMs will be able to learn general reasoning.</p>
<p>I wanted to test this claim with SAT problems. Why SAT? Because solving SAT problems require applying very few rules consistently. The principle stays the same even if you have millions of variables or just a couple. So if you know how to reason properly any SAT instances is solvable given enough time. Also, it's easy to generate completely random SAT problems that make it less likely for LLM to solve the problem based on pure pattern recognition. Therefore, I think it is a good problem type to test whether LLMs can generalize basic rules beyond their training data.</p>
<h2>What is SAT?</h2>
<p>SAT (short for &quot;satisfiability&quot;) is a logic problem that given a boolean formula, it asks whether the boolean formula has an assignment that makes the problem <code>true</code>. An example boolean formula is:</p>
<pre><code>(a || b) &amp;&amp; !a
</code></pre>
<p>This formula is satisfiable because if we set to <code>b</code> to <code>true</code> and <code>a</code> to <code>false</code>, then the whole formula is <code>true</code>. All other assignments make the formula <code>false</code>, but it doesn't change that the formula is satisfiable as long as there is at least one assignment makes the formula <code>true</code>.</p>
<p>An unsatisfiable formula is:</p>
<pre><code>b &amp;&amp; !b
</code></pre>
<p>No matter what you assign to the <code>b</code>, it will be <code>false</code> since either the left or the right of the <code>&amp;&amp;</code> operator is <code>false</code>.</p>
<h3>CNF Form</h3>
<p>There is a special form for boolean formulas called &quot;Conjunctive Normal Form&quot; (CNF). A problem in this form consists of clauses connected with and operators, where each clause only contains variables connected with or operators. The variables can appear negated, but only variables can be directly negated, something like <code>!(a &amp;&amp; b)</code> is not allowed. An example boolean formula in CNF form is:</p>
<pre><code>(a || b || c) &amp;&amp;
(!a || d || e) &amp;&amp;
(e || g || x)
</code></pre>
<p>SAT solvers usually expect boolean formulas in this form, because they are specialized to solve problems in this form efficiently. I decided to use this form to validate results of the LLM output with a SAT solver.</p>
<h2>Testing Approach</h2>
<p>My approach is very simple:</p>
<ol>
<li>Generate random SAT instances, both SAT and UNSAT.</li>
<li>Feed the SAT instance to the LLM.</li>
<li>Verify the output.</li>
</ol>
<h3>Generating SAT problems</h3>
<p>For the test to be fair for LLMs, the SAT instance should be reasonably large, but not too big. I can't just give SAT problems with thousands of variables. But also it shouldn't be too easy.</p>
<p>I learned that for 4-SAT, if clause to variable ratio is more than 10, the generated problems become difficult to solve, and the likelihood of formula to be SAT or UNSAT is close to 50%. So I generated 3 types of formulas:</p>
<ol>
<li>SAT problem with 10 variables and 200 clauses</li>
<li>SAT problem with 14 variables and 126 clauses</li>
<li>UNSAT problem with 10 variables and 200 clauses</li>
</ol>
<p>I used <a href="https://cnfgen.readthedocs.io/en/latest/">cnfgen</a> to generate SAT instances using the following command:</p>
<pre><code>cnfgen -q randkcnf 4 $VARIABLES $CLAUSES
</code></pre>
<p>This command outputs the formula in <a href="https://jix.github.io/varisat/manual/0.2.0/formats/dimacs.html">dimacs format</a>, which is a standard format for CNF supported by every SAT solver. This makes it possible to validate LLM decision with another program.</p>
<h3>Models</h3>
<p>I used https://openrouter.ai to test multiple models without having to register to different LLM providers.</p>
<p>I tested following models:</p>
<ul>
<li>Gemini 3 Pro</li>
<li>GPT 5.2 Mini</li>
<li>GPT 5.2</li>
</ul>
<p>For each model reasoning was enabled, and the reasoning effort is set to high. I included GPT 5.2 because it could be argued that it can reason better than mini. However, I couldn't test GPT 5.2 as much as the other models because it was too costly. Gemini 3 Pro was costly as well, but it didn't spend as much time as GPT 5.2 during reasoning which made it more affordable in my experience.</p>
<p>For each model, I used the same system prompt:</p>
<pre><code>The user will give a CNF in dimacs format.
Determine if it's satisfiable or not WITHOUT USING ANY EXTERNAL TOOLS.
Use your own reasoning. Don't stop even if the formula is too large just try to solve it manually.
After determining the result output a JSON with two fields:
    - satisfiable: Boolean. True if the formula is satisfiable
    - assignment: Array of booleans. If the formula is satisfiable provide an assignment for each variable from 1 to N. If the formula is not satisfiable this field is null.

EXAMPLE INPUT: 
p cnf 10 10
-7 9 10 0
7 8 9 0
-7 -9 10 0
-2 3 5 0
-4 -6 -8 0
-1 3 -5 0
1 -3 -8 0
4 -5 -10 0
4 -7 -8 0
-2 -5 8 0

EXAMPLE JSON OUTPUT:
{
	&quot;satisfiable&quot;: true,
	&quot;assignment&quot;: [false, false, false, false, false, false, false, false, false, false]
}
</code></pre>
<h3>Testing LLM Output</h3>
<p>I used <a href="https://github.com/Z3Prover/z3">z3</a> theorem prover to assess LLM output, which is a pretty decent SAT solver. I considered the LLM output successful if it determines the formula is SAT or UNSAT correctly, and for SAT case it needs to provide a valid assignment. Testing the assignment is easy, given an assignment you can add a single variable clause to the formula. If the resulting formula is still SAT, that means the assignment is valid otherwise it means that the assignment contradicts with the formula, and it is invalid.</p>
<p>Initially I aimed to test with at least 10 formulas for each model for SAT/UNSAT, but it turned out to be more expensive than I expected, so I tested ~5 formulas for each case/model. First, I used the openrouter API to automate the process, but I experienced response stops in the middle due to long reasoning process, so I reverted to using the chat interface (I don't if this was a problem from the model provider or if it's an openrouter issue). For this reason I don't have standard outputs for each testing, but I linked to the output for each case I mentioned in results.</p>
<h2>Results</h2>
<p>All formulas used in this test can be found <a href="https://github.com/onsah/llm-sat-testing/tree/main/formulas">here</a>.</p>
<h3>Gemini 3 Pro</h3>
<p>As a frontier flagship model, it was disappointing. It got no successful outcome. It seemed that it didn't reason thoroughly even though the reasoning was enabled, and the level set to high.</p>
<p>For SAT problems with 10 variables and 200 clauses, it usually output SAT as expected, but the assignment was never valid (Examples: <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gemini3pro/sat_10_200_1.txt">first</a>, <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gemini3pro/sat_10_200_2.txt">second</a>). Once it claimed <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gemini3pro/sat_10_200_3.txt">a SAT formula was UNSAT</a>. For this reason I didn't bother testing with more variables for the SAT case.</p>
<p>For UNSAT problems with 10 variables and 200 clauses, it always claimed that the formula is SAT and made up assignments (See <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gemini3pro/unsat_10_200_1.json">this example</a>).</p>
<h3>GPT 5.2 Mini</h3>
<p>Surprisingly, as a smaller model it performed better than Gemini 3 Pro. It found some valid assignments for SAT formulas, but has the same issue of making up assignments for UNSAT formulas.</p>
<p>For SAT problems with 10 variables and 200 clauses, sometimes outputted UNSAT because it couldn't find any satisfying assignment, and it would take a lot more time to find one, which is logically sound. I don't consider this as bad reasoning as it is about performance. So I tried it with only 100 clauses and it <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gpt5.2-mini/sat/formula_10_100_1.json">successfully found valid assignments</a>.</p>
<p>For UNSAT problems with 10 variables and 200 clauses, it had the same issue as Gemini 3 Pro of <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gpt5.2-mini/unsat/formula_10_100_1.json">making up assignments</a>.</p>
<h3>GPT 5.2</h3>
<p>This one was a lot better than others. For every SAT problem with 10 variables and 200 clauses it was able to find a <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gpt5.2/sat/formula1.txt">valid</a> <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gpt5.2/sat/formula3.txt">satisfying</a> <a href="https://github.com/onsah/llm-sat-testing/blob/main/tests/gpt5.2/sat/formula4.txt">assignment</a>. Therefore, I pushed it to test with 14 variables and 100 clauses, and it got half correct among 4 instances (See files with prefix <code>formula14_</code> in <a href="https://github.com/onsah/llm-sat-testing/tree/main/tests/gpt5.2/sat">here</a>). Half correct sounds like a decent performance, but it is equivalent to random guessing.</p>
<p>For UNSAT problems with 10 variables and 200 clauses it had the same issue as others: <a href="https://github.com/onsah/llm-sat-testing/tree/main/tests/gpt5.2/unsat">making up assignments</a>.</p>
<h2>Conclusion</h2>
<p>Testing LLM reasoning abilities with SAT is not an original idea; there is a recent <a href="https://arxiv.org/abs/2505.14615">research</a> that did a thorough testing with models such as GPT-4o and found that for hard enough problems, every model degrades to random guessing. But I couldn't find any research that used newer models like I used. It would be nice to see a more thorough testing done again with newer models.</p>
<p>Even though my dataset is very small, I think it's sufficient to conclude that LLMs can't consistently reason. Also their reasoning performance gets worse as the SAT instance grows, which may be due to the context window becoming too large as the model reasoning progresses, and it gets harder to remember original clauses at the top of the context. A friend of mine made an observation that how complex SAT instances are similar to working with many rules in large codebases. As we add more rules, it gets more and more likely for LLMs to forget some of them, which can be insidious. Of course that doesn't mean LLMs are useless. They can be definitely useful without being able to reason, but due to lack of reasoning, we can't just write down the rules and expect that LLMs will always follow them. For critical requirements there needs to be some other process in place to ensure that these are met.</p>
]]></description>
      </item>
      
      <item>
         <title>Understanding C++ Ownership System</title>
         <link>https://blog.aiono.dev/posts/understanding-c++-ownership-system.html</link>
         <guid>https://blog.aiono.dev/posts/understanding-c++-ownership-system.html</guid>
         <pubDate>19 Jan 2026 00:00:00 GMT</pubDate>
         <description><![CDATA[<p>I recently started using C++ at my <code>$DAY_JOB</code> and, along with that, decided to study C++ again. I think writing down your understanding is the best way to learn a topic. One part I find that is hard to understand in C++ is how the object ownership model works because it's not a single concept but a collection of a couple of smaller concepts. By ownership I mean creating and destroying objects, giving references to an object, and transferring ownership of an object. There is no one guide that covers everything.</p>
<p>These concepts are very important to write and read modern C++ (though I doubt if C++11 is still considered &quot;modern&quot;). Even if you just want to write C with Classes-style C++, you will probably use standard containers like <code>std::vector</code>, which requires an understanding of C++ ownership related features such as RAII, references, and move semantics to use it properly. Without knowing those, you simply can't have the correct memory model for C++, resulting in buggy programs full of undefined behaviors and inefficient programs due to unnecessary copying. By knowing these concepts, you can both avoid introducing bugs due to lack of understanding and reason about programs better.</p>
<p>This writing is my understanding of C++ ownership model. I think it can be useful to you if you have a basic level understanding of C++ and you want to learn more, or you are familiar with C++ but never learned the concepts and terminology formally.</p>
<h2>Who owns the Object?</h2>
<p>In C++, every object has an owner, which is responsible for cleaning up the data once it's not used anymore. If you come from garbage collected languages, the concept of ownership may seem strange to you. But consider the following code:</p>
<pre><code class="language-cpp">char* get_name(File* file);
</code></pre>
<p>This is a function that returns the file's name as a C-style string. What's not documented though, is who is supposed to deallocate the returned string. In this case there are two possibilities:</p>
<ol>
<li>The function allocates new memory, and the caller must deallocate it once it's no longer being used.</li>
<li><code>file</code> has a property that holds the file's name, and the function only returns the address of this property. The caller <em>must not</em> deallocate it.</li>
</ol>
<p>Depending on which one is the case, the caller must act differently. This is because the <em>owner</em> of the data is different between these two cases. In the first case, owner is the variable assigned to the function's return value, and in the second, owner is the <code>file</code> variable. If the latter is the case, the variable assigned to the return value <em>borrows</em> the data owned by <code>file</code>.</p>
<p>In a garbage collected language, you don't have this distinction because, in a sense every variable is a borrower, and the owner is the garbage collector (GC). The GC just ensures that the data is allocated as long as there is a reference (borrower) to it. But at the same time every variable can be considered an owner since holding a variable keeps the data alive. C++ doesn't have a garbage collector, but it has a mechanism to automate some parts of object creation and destruction.</p>
<h2>Creating and Destroying Objects</h2>
<p>When you declare a variable to an object in C++, it will create the object, and the variable will be the owner of the object. If the object has a destructor (a special function that destroys the resources represented by the object), it's automatically destroyed when the block of the variable ends. This technique of connecting resources to variables is called RAII<sup><a href="#fn-1" id="ref-1-fn-1" role="doc-noteref" class="fn-label">[1]</a></sup>. The span that object is alive is called <em>the lifetime</em> of the object.</p>
<p>In modern C++, it is advised that you create objects with destructors so the resources are cleaned up automatically at the end of their lifetime. To see why, consider this example:</p>
<pre><code class="language-cpp">void foo(std::size_t buffer_size) {
  // Allocate some memory
  char* buffer = new char[buffer_size];
  
  int result = 0;
  try {
    while (has_more) {
	  read(buffer.get());
	  result += process(buffer.get());
    }
  } catch (std::exception e) {
    delete buffer;
    throw e;
  }
  delete buffer;
  return result;
}
</code></pre>
<p>The code essentially reads some data and writes it to <code>buffer</code>, and then adds the processed output of the <code>buffer</code> into <code>result</code>. Assume that <code>read</code> may <code>throw</code>, which means that it's possible to never execute the <code>delete</code> statement before <code>return</code>. To handle this case, then we must write a <code>try catch</code> block to delete the <code>buffer</code> in case an exception occurs and then re<code>throw</code> the exception.</p>
<p>Instead of using raw <code>char*</code>, we can use an object with a destructor called <code>unique_ptr</code>. This class owns a pointer and destroys it once its lifetime ends:</p>
<pre><code class="language-cpp">void foo(std::size_t buffer_size) {
  // Allocate some memory
  std::unique_ptr&lt;char[]&gt; buffer = std::make_unique&lt;char[]&gt;(buffer_size);
  
  int result = 0;  
  while (has_more) {
    read(buffer.get());
    result += process(buffer.get());
  }
  
  // `s` will be freed before returning
  return result;
}
</code></pre>
<p>We don't need to write any <code>free</code> or <code>delete</code> because the <code>buffer</code> is a <code>unique_ptr</code><sup><a href="#fn-2" id="ref-1-fn-2" role="doc-noteref" class="fn-label">[2]</a></sup>. This is a &quot;smart&quot; pointer type that de-allocates the memory once the block it declared ends (this is related to <a href="#lifetime">lifetime</a>, which I will talk about shortly). Note that even if the body <code>throw</code>s, the cleanup process will still work. This may not seem like a big issue in the scale of this code, but in a large function, resource cleanup gets messy real quick. In fact, this is one of the few use cases where it still makes sense to use <code>goto</code> in <code>C</code> today.</p>
<p>RAII<sup><a href="#fn-1" id="ref-2-fn-1" role="doc-noteref" class="fn-label">[1]</a></sup> (Resource Acquisition Is Initialization) basically means constructing a value means creating the resource it represents, and consequently when the value is no longer reachable, destroying the resource. To make it more concrete, consider using a heap memory, but there are other resource types like files, mutex locks etc... In the absence of a garbage collector, one must deallocate every allocated heap memory manually. With RAII one can make it handled automatically.</p>
<p>So RAII is very convenient, but is it magic? It is certainly not. RAII calls the object's destructor function when the object's lifetime ends. So if you hold a reference or pointer to an object that had its lifetime ended, <a href="https://en.cppreference.com/w/cpp/language/reference.html#Dangling_references">you are in undefined behavior territory</a>. So unlike garbage collectors, RAII without lifetime analysis doesn't protect you from accessing dangling references, even though it's still an improvement over manual allocation and deallocation.</p>
<h3>Destructors</h3>
<p>Destructors are basically functions that is executed when the object's lifetime ends. They are supposed to clean whatever resources were created in the object's constructor. Notice the &quot;supposed to&quot;, because this part depends on the programmer to implement it correctly. For instance, if your class allocates a memory in the constructor and does nothing in the destructor, it will just leak memory. Therefore one must ensure everything is cleaned properly in the destructor of a class.</p>
<p>The name of a destructor function is <code>~A</code> where <code>A</code> is the class name. When such a function is defined, it is automatically inserted to the end of the scope where a variable is no longer used anymore. To make it more concrete, consider the following mutex class:</p>
<pre><code class="language-cpp">class RAIIMutex {
private:
  std::mutex&amp; mutex;
public:
  RAIIMutex(std::mutex&amp; mutex): mutex{mutex} {
    mutex.lock();
  }
  
  ~RAIIMutex() {
    mutex.unlock();
  }
}
</code></pre>
<p>Then we can use it as following:</p>
<pre><code class="language-cpp">char* global_variable = ...;
std::mutex global_variable_mutex = ...;

void foo() {
  RAIIMutex guard(global_variable_mutex);
  
  // Can access global_variable safely
  process(global_variable);
  
  // Will automatically drop the lock here
}
</code></pre>
<p>As you see, destructors are used by RAII to deallocate the resource once the variable is no longer accessible, which leads us to the lifetime concept.</p>
<h2>Lifetime</h2>
<p>In C++, every object and reference has a lifetime, which means any object or reference has a point in time where its lifetime begins and ends. What does this mean? This may seem like a silly concept if you are used to garbage collected languages, because in those languages you rarely think, &quot;when is this object not usable anymore?&quot;. You only create objects, and as long as you refer to an object, it's usable by definition. However, in C++ you <em>must</em> think about when an object stops being usable because it's possible that you refer to it through a variable name, pointer or reference, but the object is already destroyed.</p>
<p>Local variables, which are by far the most used variable type in most programs, begin their lifetime in the block they are declared in and end it when the block ends. If we go back to the RAII example, the lifetime of the <code>buffer</code> variable starts in the beginning of <code>foo</code> (because it's declared in <code>foo</code>'s block) and is deallocated at the end of the block, which is the end of <code>foo</code>.</p>
<p>There are other types of variables with different lifetime behavior<sup><a href="#fn-3" id="ref-1-fn-3" role="doc-noteref" class="fn-label">[3]</a></sup> such as <code>static</code> variables, but it's out of scope for this writing.</p>
<p>One important observation is that, if there is an object that is reaching to the end of its lifetime, we can <em>reuse</em> its resources for another object since otherwise they will be destroyed anyway. This is essential to understanding <a href="#move">moves</a>.</p>
<h2>Pointers/References</h2>
<p>In many cases, you want to pass a variable to another function but don't want to copy the whole data. In this case you need to pass a pointer or reference to that variable. From now on I will only talk about references but almost everything equally applies to pointers as well. Analogous to normal variables, references also have their lifetimes. And intuitively, reference's lifetime must always be equal or smaller than the object's lifetime that reference points to. Otherwise you would be having a reference to an object that is already destroyed and as you may know that is undefined behavior.</p>
<p>A simple way to ensure this is to never store a reference passed to function such that the reference is used after function returns. Let me give an example. Consider:</p>
<pre><code class="language-cpp">size_t bar(const std::vector&lt;int&gt;&amp; vec) {
  size_t result = 0;
  for (auto i : vec) {
    // Do something with i
  }
  return result;
}
</code></pre>
<p>This function is totally safe since it only uses <code>vec</code> to calculate a result. Once the function returns, the reference doesn't live anywhere else. But consider this:</p>
<pre><code class="language-cpp">class B {
std::vector&lt;int&gt;&amp; vec;
public:
  void set_vec(std::vector&lt;int&gt;&amp; vec) {
    this-&gt;vec = vec;
  }

  size_t bar() {
	  size_t result = 0;
	  for (auto i : vec) {
	    // Do something with i
	  }
	  return result;
  }
}

int main() {
  B b;
  if (some_condition) {
    std::vector&lt;int&gt; vec{1, 2, 3};
    b.set_vec(vec);
  }
  b.bar();
}
</code></pre>
<p>In this code, <code>main</code> creates an instance of <code>B</code> and conditionally sets it's reference variable to <code>vec</code> inside the <code>if</code>. However, then it calls <code>b.bar</code>, which refers to <code>vec</code> that is already destroyed at the end of the if block. Essentially the problem is that the lifetime of <code>vec</code> is limited to the if block but <code>b</code> has a larger lifetime which leads to a dangling reference.</p>
<p>In short, always ensure that references point to objects with a large enough lifetime to prevent painful debugging sessions.</p>
<h2>Move</h2>
<p>Up until this point, we learned how to create objects, copy them and hold references to them while they are alive. These are enough to write many useful programs, as C++ until C++11 only had these features. But there is one missing piece: transferring resources from one object to another. You can emulate moves with pointers to some degree (since you can just copy a pointer and reassign the old pointer to something else), but there are some things that's not possible to do without moves.</p>
<p>Consider how <code>std::vector</code> reallocates it's buffer. When you grow a vector you need to copy the object's from the old buffer to the new buffer. A simplistic implementation would be:</p>
<pre><code class="language-cpp">template &lt;typename T&gt;
void vector&lt;T&gt;::grow(size_t new_capacity) {
  T* new_buffer = new T[new_capacity];
  for (size_t i = 0; i &lt; this-&gt;size; ++i) {
    new_buffer[i] = this-&gt;buffer[i];
  }
  T* old_buffer = this-&gt;buffer;
  this-&gt;buffer = new_buffer;
  this-&gt;capacity = new_capacity;
  delete[] old_buffer;
}
</code></pre>
<p>If you look into the in the <code>for</code> loop, you see that we make a <em>copy assignment</em> (because the value category of <code>this-&gt;buffer[i]</code> is <code>lvalue</code> but don't worry if you don't know what this means) from the old buffer to the new buffer for each element. This is a cheap operation if the type of the vector is a simple type but if it's holding large heap allocated objects it means every value in vector is <em>duplicated</em>. Since the old buffer is immediately deleted after the copy this is really unnecessary. As these objects are going to be destroyed anyway, it would actually not a problem to <em>steal</em> the contents of the objects in the old buffer.</p>
<p>You may think, &quot;can't I just <code>mempcy</code> the old buffer to new buffer?&quot;. It wouldn't work because it would call destructor both in the old and new buffer for every <code>memcpy</code>ed object. You can't also zero the old buffer since you don't know if that's a valid object representation. Also it's quite possible that object is self referential. In that case you would end up with a dangling pointer.</p>
<p>This is where the concept of move comes into the picture. By introducing move into C++ we can now steal resources from an object that is about to be destroyed, thus avoiding making unnecessary copies. Using <code>std::move</code>, we can rewrite the copy loop as:</p>
<pre><code class="language-cpp">for (size_t i = 0; i &lt; this-&gt;size; ++i) {
    new_buffer[i] = std::move(this-&gt;buffer[i]);
}
</code></pre>
<p>Notice how the only difference is that we call <code>std::move</code> with the variable we want to <em>move from</em> and assign it's return value to the variable we want to <em>move to</em>. With this change the source object's resources will be reused in the new buffer, thus eliminating the unnecessary copying.</p>
<p>We now know how to move objects, so can we just spam <code>std::move</code> to any object type to achieve move behavior? The answer is no, unlike <a href="https://cel.cs.brown.edu/crp/idioms/constructors/copy_and_move_constructors.html#move-constructors">Rust move semantics</a>, C++ move is something of a convention than a pure language feature, but there are also few language features playing a role.</p>
<p>One way to understand how something works is to simply look how it's implemented. So let's see, here is an overly simplified example <code>std::move</code> implementation:</p>
<pre><code class="language-cpp">template&lt;class T&gt;
typename T&amp;&amp; move(T&amp; t) {
  return static_cast&lt;T&amp;&amp;&gt;(t);
}
</code></pre>
<p>Were you expecting something <em>this</em> simple? Probably not, but that's essentially what <code>std::move</code> is. In itself <code>std::move</code> doesn't actually move anything (great naming right?). It's just a glorified cast from an lvalue reference <code>T&amp;</code> to an <em>rvalue reference</em> <code>T&amp;&amp;</code>. Previously, when we only talked about references in general what we actually meant was lvalue references. An rvalue reference is <em>by convention</em> a reference to an object that is about to expire, one <em>can</em> <em>steal</em> resources from it. There is nothing instrintic to the language that makes rvalue references stealing, and lvalue references non-stealing. We can treat them as opposites and write code that way, but that would be working against the whole standard library and overload resolution rules so it doesn't make sense to do.
Existence of rvalue references allows writing overloads that normally copy to instead reuse the resources from the rvalue reference parameter. For example, consider two constructors for <code>std::string</code>:</p>
<pre><code class="language-cpp">std::string(const std::string&amp; other);
std::string(std::string&amp;&amp; other);
</code></pre>
<p>First constructor takes an lvalue reference, so it will allocate a new buffer and copy the contents from <code>other</code>. Since it is only copying it's also <code>const</code>. While second constructor takes an rvalue reference, thus can simply assign the buffer from <code>other</code> to the new object, and assign <code>other</code>'s buffer to <code>nullptr</code> (Therefore can't be <code>const</code>). When you normally pass an lvalue to a function, the overload with the lvalue reference will be choosen. But if you first call <code>std::move</code> then the second constructor will be picked. So that's essentially what <code>std::move</code> does, it makes you pick the rvalue reference overload among the other options.</p>
<h2>Conclusion</h2>
<p>This was a long one, so if you came this far, thank you! I hope now you have an understanding of the ownership system in C++. Essentially it's bunch of seemingly unrelated features that together forms a coherent (!) system. There is a lot more gritty details about this stuff, but for the most cases you don't need to know that well.</p>
<p>Thanks to <a href="https://www.rugu.dev/">Uğur</a> for his feedback on the initial draft.</p>
<h2>Further Reading</h2>
<p>If you liked what you read and want to dive deeper, I recommend looking into these:</p>
<ul>
<li><a href="https://cbarrete.com/move-from-scratch.html">https://cbarrete.com/move-from-scratch.html</a></li>
<li><a href="https://www.youtube.com/watch?v=d5h9xpC9m8I">A video about C++ value categories</a></li>
</ul>
<section role="doc-endnotes"><ol>
<li id="fn-1">
<p>https://en.cppreference.com/w/cpp/language/raii.html</p>
<span><a href="#ref-1-fn-1" role="doc-backlink" class="fn-label">↩︎︎<sup>1</sup></a><a href="#ref-2-fn-1" role="doc-backlink" class="fn-label">↩︎︎<sup>2</sup></a></span></li><li id="fn-2">
<p>https://en.cppreference.com/w/cpp/memory/unique_ptr.html</p>
<span><a href="#ref-1-fn-2" role="doc-backlink" class="fn-label">↩︎︎</a></span></li><li id="fn-3">
<p>https://en.cppreference.com/w/c/language/storage_class_specifiers.html</p>
<span><a href="#ref-1-fn-3" role="doc-backlink" class="fn-label">↩︎︎</a></span></li></ol></section>
]]></description>
      </item>
      
      <item>
         <title>Algebraic Types are not Scary, Actually</title>
         <link>https://blog.aiono.dev/posts/algebraic-types-are-not-scary,-actually.html</link>
         <guid>https://blog.aiono.dev/posts/algebraic-types-are-not-scary,-actually.html</guid>
         <pubDate>30 Aug 2025 00:00:00 GMT</pubDate>
         <description><![CDATA[<p>You may have heard the term algebraic types before, which initially sounds like an advanced concept, that only someone with a PhD in programming languages can understand. Quite the contrary, algebraic types is a very simple and helpful concept about programming in general. Anyone who knows basic algebra could understand what algebraic types are.</p>
<p>In this article I aim to provide an explanation of algebraic types for the working programmer. I intentionally avoid any terminology that a regular programmer may not know about. I hope by the end of the article you know what algebraic types are and can use it in real programming and spot it where it appears.</p>
<h2>Types as Sets</h2>
<p>What is a type, really? For instance, when we write <code>int</code>, what does it mean? One useful way to think about it is to treat types as sets. In this perspective, every type is treated as a set of possible values that is compatible with the type. For instance, <code>bool</code> is a type that has only <code>true</code> and <code>false</code> values. In OCaml <code>bool</code> is defined as:</p>
<pre><code class="language-ocaml">type bool = true | false
</code></pre>
<p>In the left hand side we define the type <code>bool</code>. The right side provides the possible values, separated with <code>|</code>.</p>
<p>For integers, this is <code>0</code>, <code>1</code> or any other integer value. It's a bit more difficult to define integers directly as a regular type because in this case there are infinitely many values that integer can take. Writing all these is impossible. But assuming it was possible, we could write:</p>
<pre><code class="language-ocaml">type int = ... | -3 | -2 | -1 | 0 | 1 | 2 | 3 | ...
</code></pre>
<p>In practice, integer types are usually limited to some finite range (but still too large) but this is not related to what we are discussing here. Strings are very similar to integers from this perspective.</p>
<p>What about <code>void</code> type? What are the values that it accepts? In some languages it's not obvious, but we can think <code>void</code> as a type with only a single possible value. In OCaml for instance, <code>unit</code> corresponds to <code>void</code>. It's defined as:</p>
<pre><code class="language-ocaml">type unit = ()
</code></pre>
<p>In C, C++ or Java, <code>void</code> is treated differently than other types which makes it awkward to use in some cases. If we consider it as just any other type, there is no real need to make an exception for <code>void</code>. This simplifies the type system as well as the implementation of the programming language. I will give some examples to this after we understand <a href="#algebraic-types-is-not-scary-actually">Algebraic Types</a>.</p>
<p>Another interesting case is non-termination. What is the type of a while loop that never returns? Well, using the set perspective, if the expression returns no value, maybe it's type is a type which has no possible values? Note that this type is <em>different</em> than <code>void</code> because <code>void</code> has one value that it can take but this type can take none. Sometimes this type is called <code>never</code>, as in it's never possible to have a value of this type. We can also trivially define this type as:</p>
<pre><code class="language-ocaml">type never
</code></pre>
<p>Since there is no value for this type, it is <em>impossible</em> to have a value of this type. Thus we can use it as the type of expressions or functions that doesn't terminate. Because, if it would terminate and return a value we would get a type error indicating that the value doesn't conform to the specified type.</p>
<h2>Algebraic Types are Just Elementary School Algebra</h2>
<p>Using this view of types as sets of values, it's really easy to understand algebraic types. In fact, it's actually based on the algebra you learned in elementary school!</p>
<p>What is algebra on numbers? It's addition, multiplication, subtraction and division. Algebraic types are exactly that, it's basically <em>doing algebra over types</em>. So basically it's addition and multiplication over types.</p>
<h3>Product Types</h3>
<p>Let's start with the more familiar one. If you have two types <code>T1</code> and <code>T2</code>, what other types you can have with it? Well, you can have a value that contains from both of these types, one from <code>T1</code> and one from <code>T2</code>. Similar to how a <code>struct</code> or <code>class</code> works in mainstream languages. We could express this in Java as:</p>
<pre><code class="language-java">class Pair {
    T1 first;
    T2 second;
}
</code></pre>
<p>In the algebraic type terminology, this is called a <em>product type</em>. The reason is simple, when you combine two types, the resulting type contains every value whose parts are the values from the respective types. If the first type has <code>N</code> values, and the second has <code>M</code> values. Let's assume both to be enum types, with <code>N</code> having 2 and <code>M</code> having 3 variants. If we create a pair type from <code>N</code> and <code>M</code>, we could have <code>6</code> different values. Because we can choose 2 from <code>N</code> and 3 from <code>M</code>, which results with <code>2 * 3 = 6</code>. Hence, a pair type in the general case has <code>N * M</code> many values, hence the term product.</p>
<p>Every mainstream language supports this notion, because it's a very common use case, I am sure that this doesn't need any convincing. However, most languages doesn't support combining two types as a first class construct such as tuple types, therefore one has to explicitly define a new type for every combination. In practice, this lack leads to worse API designs, like the pattern of using pointers/references <sup><a href="#fn-1" id="ref-1-fn-1" role="doc-noteref" class="fn-label">[1]</a></sup> to return multiple values or Go's multiple return values <sup><a href="#fn-2" id="ref-1-fn-2" role="doc-noteref" class="fn-label">[2]</a></sup>, because to return multiple arguments one has to create a custom type. Not having product types forces you to circumvent it with more specialized constructs that creates accidental complexity. Supporting product types as first class (See Rust and OCaml as examples) makes the language simpler and more unified, reducing the cognitive load for the user <sup><a href="#fn-3" id="ref-1-fn-3" role="doc-noteref" class="fn-label">[3]</a></sup>.</p>
<h3>Sum Types</h3>
<p>Now this part is a bit less apparent if you never used a functional language. But it's a really a common use case in programming that, you probably seen a problem before where you could use sum types.</p>
<p>A sum type is a type composed of two other types, where the values can be <em>either</em> from the first type or the second type. For instance if you want to denote a fallible arithmetic operation, where the result is <code>int</code> if successful and a <code>string</code> containing the error message if not, the type of this result is <code>int</code> or <code>string</code>.</p>
<p>The name sum comes from the fact that, similar to products, if you create a sum type from types <code>N</code> and <code>M</code>, you get a type where there are <code>N + M</code> different possible values. Because you can have <code>N</code> options from the first and <code>M</code> option from the second. It's similar to logical or in the sense that, a value of a sum type is actually from the first type <em>or</em> the second type.</p>
<p>Sum types appear commonly in real life. A value that can be <code>null</code> is a sum type, usually called <code>Option</code> or <code>Maybe</code> in programming languages. In OCaml it is defined as:</p>
<pre><code class="language-ocaml">type a option = Some of a | None
</code></pre>
<p>The <code>a</code> denotes a generic type, if you are familiar with Java, it is equivalent to <code>Option&lt;A&gt;</code>. First construct <code>Some</code> is the case where a value is present, while <code>None</code> corresponds to <code>null</code> in imperative languages. This is not just an example, <code>option</code> type is very commonly used in  programs written in OCaml and other functional languages. <code>null</code> being at the type level prevents many runtime errors and reduces verbosity (you don't have to write <code>null</code> checks everywhere).</p>
<p>Another example is modeling errors. In Go, when a function can return an error, it's idiomatic to return it as the second value. By convention, either the first value or the second value is <code>nil</code> (Go's <code>null</code> value). However, this convention is implicit and in nowhere is enforced. So when you return two values, you have 4 cases, but you actually assume only two cases can happen in practice. If both values are not <code>null</code> or <code>null</code>, that would violate the assumption. We can summarize it in a table:</p>
<div role="region"><table>
<tr>
<th>Value 1</th>
<th>Value 2</th>
<th>Assumed</th>
</tr>
<tr>
<td><code>present</code></td>
<td><code>null</code></td>
<td>yes</td>
</tr>
<tr>
<td><code>null</code></td>
<td><code>null</code></td>
<td>yes</td>
</tr>
<tr>
<td><code>present</code></td>
<td><code>present</code></td>
<td>no</td>
</tr>
<tr>
<td><code>null</code></td>
<td><code>null</code></td>
<td>no</td>
</tr>
</table></div><p>The problem is, this invariant is never validated by the type checker and therefore the user has to be aware of the convention, which creates unnecessary cognitive load for the user. For instance, <code>io.Reader</code> interface may return <code>EOF</code> error <em>while also returning some data</em>. This is not what the general Go programmers assume to be the case, since they expect either <code>err</code> or <code>val</code> to be non-<code>nil</code>. This discrepancy causes <a href="https://github.com/golang/go/issues/52577">real life</a> <a href="https://www.reddit.com/r/golang/comments/u8wsnq/i_was_using_ioreader_wrongly/">bugs</a> even though <a href="https://pkg.go.dev/io#Reader">it's documented</a>.</p>
<p>Another disadvantage is that the programmer can't know the product is in fact intended to model a sum type unless it's in the documentation or they read the whole code. Both of these create more cognitive load compared to sum type in the signature. Moreover, the lack of sum types cause real life bugs in general, like anything that requires human validation, such as <a href="https://nicolashery.com/decoding-json-sum-types-in-go/#my-first-nil-pointer-panic-in-go-was-due-to-lack-of-sum-types">this one</a>.</p>
<p>Instead, we could simply use a sum type to denote it's <em>either</em> a success with the result value, or an error with the error information. In OCaml, there is <code>result</code> type exactly for that:</p>
<pre><code class="language-ocaml">type (a, b) result = Ok of a | Error of b
</code></pre>
<p>When we have a value of type <code>error</code>, the type checker enforces that only two desired conditions can happen, and the undesired conditions are <em>impossible</em> to represent in code, making the code simpler and less prone to errors.</p>
<h3>Using Algebraic Types in Practice</h3>
<p>To demonstrate the practical benefits of algebraic types, lets write an interpreter for arithmetic expressions. We will only have integers and arithmetic operators. We will not go through parsing arithmetic expressions as it's not related to the topic.</p>
<p>The type of expressions follows naturally from the definition:</p>
<pre><code class="language-ocaml">type expr =
| Number of int
| Add of { left : expr; right: expr }
| Sub of { left : expr; right: expr }
| Mul of { left : expr; right: expr }
| Div of { left : expr; right: expr }
</code></pre>
<p>The first case denotes integers for the operands. Following cases correspond to each arithmetic operator. For instance, <code>2 + (3 * 2)</code> corresponds to:</p>
<pre><code class="language-ocaml">let e = Add { 
  left = Number 2; 
  right = Mul { 
    left = Number 3; 
    right = Number 2; 
  } 
}
</code></pre>
<p>To evaluate expressions, we can write a simple evaluator, using pattern matching:</p>
<pre><code class="language-ocaml">let rec eval (e : expr) : int =
  match e with
  | Number n -&gt; n
  | Add { left; right } -&gt;
    (eval left) + (eval right)
  | Sub { left; right } -&gt;
    (eval left) - (eval right)
  | Mul { left; right } -&gt;
    (eval left) * (eval right)
  | Div { left; right } -&gt;
    (eval left) / (eval right)
</code></pre>
<p>If you are not familiar with pattern matching, it lets us determine the which variant the value has. The possible variants come from it's type. In this case, from the definition of <code>expr</code>, we know it's either a <code>Number</code> or one of the 4 operations. For the number case, we can return it directly. In other cases, the <code>left</code> and <code>right</code> fields have type <code>expr</code>, so first we have to recursively evaluate those subterms to get their <code>int</code> value. Then we can evaluate the current expression value by using the appropriate operator.</p>
<p>How could you do this without algebraic types? Abstract methods with inheritance can be used to emulate sum types. So we could have an abstract base class <code>Expr</code> then extend it for each case:</p>
<pre><code class="language-java">abstract class Expr { abstract int eval(); }

class Number extends Expr {
    int value;
    int eval() { return value; }
}

class Plus extends Expr {
    Expr left, right;
    int eval() { return left.eval() + right.eval(); }
}

// Rest is omitted
</code></pre>
<p>The base class <code>Expr</code> has a method <code>eval</code> which should return the evaluated value of the expression. Each subclass implements it, recursively calling subexpression's <code>eval</code> method. In this case, there is no clear definition of the data structure, but it's mixed with the behavior.</p>
<p>What if we want to interpret the expressions in a different way? Say we just want to convert it to it's written form. For the inheritance based solution, we would have to add a new base method to the class, and then implement it in the subclasses, like <code>eval</code>. With algebraic types, we could write another function that performs pattern matching. Something like:</p>
<pre><code class="language-ocaml">let rec expr_to_string (e : expr) : string =
  match e with
  | Number n -&gt; string_of_int n
  | Add { left; right } -&gt;
    &quot;(&quot; ^ (expr_to_string left) ^ &quot;+&quot; ^ (expr_to_string right) ^ &quot;)&quot;
  | Sub { left; right } -&gt;
    &quot;(&quot; ^ (expr_to_string left) ^ &quot;-&quot; ^ (expr_to_string right) ^ &quot;)&quot;
  | Mul { left; right } -&gt;
    &quot;(&quot; ^ (expr_to_string left) ^ &quot;*&quot; ^ (expr_to_string right) ^ &quot;)&quot;
  | Div { left; right } -&gt;
    &quot;(&quot; ^ (expr_to_string left) ^ &quot;/&quot; ^ (expr_to_string right) ^ &quot;)&quot;
</code></pre>
<p>I don't know you but I find the latter approach better. Because in the inheritance approach the relevant behavior is far away from each other. When someone wants to understand how string conversion works, they need to jump through every class. Whereas with the pattern matching, the relevant logic stays closer. Another issue is that operations implemented as a method in the class, therefore they have full access to the object's internals. However, those operations should only access the public interface of the objects.</p>
<p>The abstract method approach is not the only alternative. The Visitor Pattern <sup><a href="#fn-4" id="ref-1-fn-4" role="doc-noteref" class="fn-label">[4]</a></sup> exists specifically to model sum types with object hierarchies <sup><a href="#fn-6" id="ref-1-fn-6" role="doc-noteref" class="fn-label">[5]</a></sup>. Using visitor pattern, we could have the following implementation:</p>
<pre><code class="language-java">abstract class Expr { 
  abstract &lt;R&gt; R accept(Visitor&lt;R&gt; visitor);
}

class Number extends Expr {
    int value;
    &lt;R&gt; R accept(Visitor&lt;R&gt; visitor) { return visitor.visit(this); }
}

class Plus extends Expr {
    Expr left, right;
    &lt;R&gt; R accept(Visitor&lt;R&gt; visitor) { return visitor.visit(this); }
}

class Mul extends Expr {
    Expr left, right;
    &lt;R&gt; R accept(Visitor&lt;R&gt; visitor) { return visitor.visit(this); }
}

class Sub extends Expr {
    Expr left, right;
    &lt;R&gt; R accept(Visitor&lt;R&gt; visitor) { return visitor.visit(this); }
}

class Div extends Expr {
    Expr left, right;
    &lt;R&gt; R accept(Visitor&lt;R&gt; visitor) { return visitor.visit(this); }
}

interface Visitor&lt;R&gt; {
  R visit(Number number);
  R visit(Plus plus);
  R visit(Mul mul);
  R visit(Sub sub);
  R visit(Div div);
}

class EvalVisitor implements Visitor&lt;Integer&gt; {
  public Integer visit(Number number) { return number.value; }
  public Integer visit(Plus plus) { return plus.left.accept(this) + plus.right.accept(this); }
  public Integer visit(Mul mul) { return mul.left.accept(this) * mul.right.accept(this); }
  public Integer visit(Sub sub) { return sub.left.accept(this) - sub.right.accept(this); }
  public Integer visit(Div div) { return div.left.accept(this) / div.right.accept(this); }
}
</code></pre>
<p>It solves the problem of having to add new methods to the base class compared to naive inheritance approach. Also the relevant logic sits inside a single place, in this case the visitor implementation. However, it's a lot more verbose than pattern matching and more difficult to understand. It has more accidental complexity <sup><a href="#fn-5" id="ref-1-fn-5" role="doc-noteref" class="fn-label">[6]</a></sup> compared to pattern matching. Essentially visitor pattern is poor man's pattern matching. As Mark Seeman said <sup><a href="#fn-6" id="ref-2-fn-6" role="doc-noteref" class="fn-label">[5]</a></sup>:</p>
<blockquote>
<p>That's not to say that these two representations are equal in readability or maintainability. F# and Haskell sum types are declarative types that usually only take up a few lines of code. Visitor, on the other hand, is a small object hierarchy; it's a more verbose way to express the idea that a type is defined by mutually exclusive and heterogeneous cases. I know which of these alternatives I prefer, but if I were caught in an object-oriented code base, it's nice to know that it's still possible to model a domain with algebraic data types.</p>
</blockquote>
<h2>Conclusion</h2>
<p>In short, for the most programming tasks you need two fundamental ways to combine types: the product and the sum. With these you can create arbitrary structures that can model real world data. Most languages have a way to express these two constructs, albeit some ways to represent it are more cumbersome such as using inheritance to emulate sum types. Using fundamental concepts you can model things in a simpler way without introducing unnecessary complexity.</p>
<h2>Credits</h2>
<p>Thanks <a href="https://www.rugu.dev/">Uğur</a> for his detailed and valuable feedback on the draft of this article.</p>
<section role="doc-endnotes"><ol>
<li id="fn-1">
<p><a href="https://stackoverflow.com/questions/2620146/how-do-i-return-multiple-values-from-a-function-in-c">How do I return multiple values from a function in C? - Stackoverflow</a></p>
<span><a href="#ref-1-fn-1" role="doc-backlink" class="fn-label">↩︎︎</a></span></li><li id="fn-2">
<p><a href="https://herecomesthemoon.net/2025/03/multiple-return-values-in-go/">Were multiple return values Go's biggest mistake?</a></p>
<span><a href="#ref-1-fn-2" role="doc-backlink" class="fn-label">↩︎︎</a></span></li><li id="fn-3">
<p><a href="https://minds.md/zakirullin/cognitive">Cognitive load is what matters</a></p>
<span><a href="#ref-1-fn-3" role="doc-backlink" class="fn-label">↩︎︎</a></span></li><li id="fn-4">
<p><a href="https://en.wikipedia.org/wiki/Visitor_pattern">Visitor Pattern</a></p>
<span><a href="#ref-1-fn-4" role="doc-backlink" class="fn-label">↩︎︎</a></span></li><li id="fn-6">
<p><a href="https://blog.ploeh.dk/2018/06/25/visitor-as-a-sum-type/">Visitor is a sum type</a></p>
<span><a href="#ref-1-fn-6" role="doc-backlink" class="fn-label">↩︎︎<sup>1</sup></a><a href="#ref-2-fn-6" role="doc-backlink" class="fn-label">↩︎︎<sup>2</sup></a></span></li><li id="fn-5">
<p><a href="https://www.cs.unc.edu/techreports/86-020.pdf">No Silver bullet</a></p>
<span><a href="#ref-1-fn-5" role="doc-backlink" class="fn-label">↩︎︎</a></span></li></ol></section>
]]></description>
      </item>
      
      <item>
         <title>Minimal Scala Container Images using Nix</title>
         <link>https://blog.aiono.dev/posts/minimal-scala-container-images-using-nix.html</link>
         <guid>https://blog.aiono.dev/posts/minimal-scala-container-images-using-nix.html</guid>
         <pubDate>25 Jul 2025 00:00:00 GMT</pubDate>
         <description><![CDATA[<p>Recently I've been working on a Scala backend as a side project. I decided to deploy it as a Docker image for portability reasons. My hosting provider supports running JARs, but I wanted something that I could host anywhere if I decided to move away.</p>
<p>There are already articles about <a href="https://medium.com/@ievstrygul/dockerizing-scala-app-3fdf08cffda4">generating Docker images for a Scala project</a> <a href="https://zendesk.engineering/using-nix-to-develop-and-package-a-scala-project-cadccd56ad06">even with Nix</a>, so why am I writing another one? The reason is that when I followed them, I ended up with a 722 MB Docker image! I found this to be unnecessarily big which motivated me to look for ways to reduce it. So this article is about building a <strong>minimal</strong> Docker image for a Scala project using Nix. Most of it can be applied to any program that runs on JVM (Java, Kotlin, etc.) as well.</p>
<p>Containerization of a JVM application feels a bit strange because one has to also bundle the JVM to execute the JAR, so it's essentially virtualization over virtualization, which also most probably runs on a virtual machine.</p>
<p>Side note: If you just want to the see the end result, you can jump directly to the <a href="#final-derivation">last section</a>.</p>
<p>Anyway... let's start.</p>
<h2>First attempt</h2>
<p>In order to containerize an <code>sbt</code> project one has to:</p>
<ol>
<li>Build a JAR with all the dependencies included. This is called &quot;über JAR&quot;. Normally JARs don't include their dependencies and load them at runtime, similar to how shared libraries work.</li>
<li>Bundle it with a JVM (Java Virtual Machine). JVM applications need a virtual machine to be executed.</li>
<li>Package the whole thing into a Docker-compatible container image.</li>
</ol>
<p>Side note: If you are not familiar with <a href="https://www.scala-sbt.org/">sbt</a> (Scala Build Tool), it's the de-facto build tool for Scala projects.</p>
<p>I am going to do all steps with Nix, since that gives me reproducible builds and I already use it for development.</p>
<p>My first attempt was the following:</p>
<pre><code class="language-nix">let
    repository = builtins.fetchTarball {
        url = &quot;https://github.com/zaninime/sbt-derivation/archive/master.tar.gz&quot;;
    };
    sbt-derivation = import &quot;${repository}/overlay.nix&quot;;
    app = sbt-derivation.mkSbtDerivation.${system} {
        pname = &quot;app&quot;;
        version = &quot;0.0.1&quot;;
        src = ./.;
        depsSha256 = &quot;sha256-06Qog8DyDgisnBhUQ9wW46WqqnhGXlakI1DSuFHkriQ=&quot;;

        buildInputs = with pkgs; [ sbt jdk23 makeWrapper ];

        buildPhase = &quot;sbt assembly&quot;;

        installPhase = ''
        mkdir -p $out/bin
        mkdir -p $out/share/java

        cp src/app/target/scala-3.*/*.jar $out/share/java

        makeWrapper ${pkgs.jdk23_headless}/bin/java $out/bin/scala-app \
            --add-flags &quot;-cp \&quot;$out/share/java/*\&quot; org.app.Application&quot;
        '';
    };
    app-container = pkgs.dockerTools.buildImage {
        name = &quot;app-container&quot;;
        tag = &quot;latest&quot;;

        copyToRoot = [ app pkgs.busybox ];

        config = { Cmd = [ &quot;/bin/${app.pname}&quot; ]; };
    };
in 
    app-container
</code></pre>
<p>Explanation:</p>
<ol>
<li><a href="https://github.com/zaninime/sbt-derivation">sbt-derivation</a> is a convenience utility to generate Nix derivations for <code>sbt</code> projects.</li>
<li><a href="https://github.com/sbt/sbt-assembly">sbt-assembly</a> lets us generate &quot;über JAR&quot; with all the necessary dependencies.</li>
<li><code>makeWrapper</code> creates a binary that wraps the JAR with a <code>java</code> binary so it looks like a regular binary from the outside. The binary comes from <a href="https://search.nixos.org/packages?channel=unstable&amp;show=jdk23_headless&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=jdk23_headless">jdk23_headless</a> package.</li>
</ol>
<p>After I built the container, I was pushing it into the hosting provider's registry, but I noticed it took a lot of time to upload it. My home connection is not really fast (I would expect better from Germany), so having to wait 15 minutes to deploy a new version of the application was very annoying. I checked the image size locally, and I saw:</p>
<pre><code>REPOSITORY                                   TAG                               IMAGE ID      CREATED        SIZE
localhost/app-container                      latest                            f0e2ad8f1167  55 years ago   722 MB
</code></pre>
<p>Ignore the obviously incorrect <code>CREATED 55 years ago</code>, the image was 722 MB! This is huge for a project like this. Annoyed by it, I went to check out the contents of the image. Using <code>docker</code>/<code>podman</code> one can export the image filesystem into a <code>.tar</code> archive:</p>
<pre><code class="language-sh">aiono ❯ docker create localhost/app-container:latest
0b08b8de863228c8211d7c844a3e84a9b03c5032f68ee582e1e0fca6caee0244
aiono ❯ TMP_DIR=$(mktemp -d)
aiono ❯ docker export 0b08b8de863228c8211d7c844a3e84a9b03c5032f68ee582e1e0fca6caee0244 &gt; &quot;$TMP_DIR/image.tar&quot;
aiono ❯ mkdir &quot;$TMP_DIR/image&quot;
aiono ❯ tar -xf &quot;$TMP_DIR/image.tar&quot; -C &quot;$TMP_DIR/image&quot;
</code></pre>
<p>Then I checked the contents of the image using <a href="https://www.man7.org/linux/man-pages/man1/du.1.html">du</a>:</p>
<pre><code class="language-sh">aiono ❯ du -sh $TMP_DIR/image/*
1,2M    /tmp/tmp.3xnIHXwY1l/image/bin
4,0K    /tmp/tmp.3xnIHXwY1l/image/default.script
0       /tmp/tmp.3xnIHXwY1l/image/linuxrc
651M    /tmp/tmp.3xnIHXwY1l/image/nix
1,2M    /tmp/tmp.3xnIHXwY1l/image/sbin
37M     /tmp/tmp.3xnIHXwY1l/image/share
</code></pre>
<p>It seems like most of the size comes from <code>/nix/store</code> which is not surprising. What's under <code>/share</code> should be the JVM. So it seems like the problem is not in the JAR since it's just 37 MB. Let's verify:</p>
<pre><code class="language-sh">aiono ❯ du -sh /tmp/tmp.3xnIHXwY1l/image/share/java/*
37M     /tmp/tmp.3xnIHXwY1l/image/share/java/scala-app-assembly-0.1.0.jar
</code></pre>
<p>Correct!</p>
<p>Let's see what are the largest directories under <code>/nix/store</code>:</p>
<pre><code class="language-sh">aiono ❯ du -sh $TMP_DIR/image/nix/store/* | sort -h | tail -n 10
484K    /tmp/tmp.3xnIHXwY1l/image/nix/store/mk9nhl6b48gpqhdbjy9ir16wrz6r3qn6-lcms2-2.17
628K    /tmp/tmp.3xnIHXwY1l/image/nix/store/ncdwsrgq6n6161l433m4x34057zq0hhf-libidn2-2.3.8
1,2M    /tmp/tmp.3xnIHXwY1l/image/nix/store/skijwg3cx0hkl5p2l5l4zz898glxi644-busybox-1.36.1
1,7M    /tmp/tmp.3xnIHXwY1l/image/nix/store/00zrahbb32nzawrmv9sjxn36h7qk9vrs-bash-5.2p37
2,0M    /tmp/tmp.3xnIHXwY1l/image/nix/store/vm18dxfa5v7y3linrg1x1q9wx41bkxwf-libunistring-1.3
2,0M    /tmp/tmp.3xnIHXwY1l/image/nix/store/w753b87diqcja7gc3kifydxdfpi967ns-libjpeg-turbo-3.1.0
9,6M    /tmp/tmirectories undep.3xnIHXwY1l/image/nix/store/l7d6vwajpfvgsd3j4cr25imd1mzb7d1d-gcc-14.3.0-lib
31M     /tmp/tmp.3xnIHXwY1l/image/nix/store/q4wq65gl3r8fy746v9bbwgx4gzn0r2kl-glibc-2.40-66
37M     /tmp/tmp.3xnIHXwY1l/image/nix/store/778xsjch86fyv4qdzznqyihcw7s5r029-scala-app-0.0.1
566M    /tmp/tmp.3xnIHXwY1l/image/nix/store/w7rphym6zk35wsx3aknbn3y7srj3x5qa-openjdk-headless-23.0.2+7
</code></pre>
<p>The winner is definitely <code>openjdk-headless</code>. Almost all of the 722 MB comes from it. So that will be the first thing we will try to reduce. Second is odd; <code>scala-app</code> is the package for our app, but we already have a copy of our app in <code>/share</code>! So it looks like a duplication, but first focus on our JDK package.</p>
<h2>Minimal Java Runtime Environments</h2>
<p>My first mistake was to bundle a full <strong>Java Development Kit (JDK)</strong> with the application. JDKs are used to <em>build</em> a JVM app but to run it, you only need <strong>Java Runtime Environment (JRE)</strong>. I searched for <code>jre</code> in <a href="https://search.nixos.org/packages?channel=25.05&amp;type=packages&amp;query=jre">Nix Search</a>. The second option was <code>jre_minimal</code>, which sounded very promising. So I made the following change:</p>
<pre><code class="language-nix">- makeWrapper ${pkgs.jdk23_headless}/bin/java $out/bin/scala-app \
+ makeWrapper ${pkgs.jre_minimal}/bin/java $out/bin/scala-app \
</code></pre>
<p>Let's see how our image size changed:</p>
<pre><code class="language-sh">aiono ❯ nix-build
aiono ❯ cat result | docker load
aiono ❯ docker images
REPOSITORY                        TAG                               IMAGE ID      CREATED        SIZE
localhost/app-container           latest                            0b25de30d43c  55 years ago   508 MB
</code></pre>
<p>It's down to 508 MB. Still very big but at least we managed to trim it down 200 MB. Let's try to run our app:</p>
<pre><code class="language-sh">aiono ❯ nix-build
aiono ❯ docker run --expose 4041 --network host localhost/app-container:latest
Exception in thread &quot;main&quot; java.lang.NoClassDefFoundError: sun/misc/Unsafe
        at ...
</code></pre>
<p>Not good. It seems like we need <code>sun.misc.Unsafe</code> but <code>jre_minimal</code> doesn't come with it. We need to fix this.</p>
<p>I don't remember how I found it, but <a href="https://nixos.org/manual/nixpkgs/stable/#sec-language-java">this section in the nixpkgs reference</a> shows how to use <code>jre_minimal</code>. Turns out, <code>jre_minimal</code> strips out <em>all</em> the standard modules to provide a minimal JRE, so you need to provide which libraries you want. Under the hood, it <a href="https://github.com/NixOS/nixpkgs/blob/3ff0e34b1383648053bba8ed03f201d3466f90c9/pkgs/development/compilers/openjdk/jre.nix#L28">uses jlink to generate a minimal JRE</a>.</p>
<p>How do we know which modules we need? Thankfully, there is a tool for that called <a href="https://dev.java/learn/jvm/tools/core/jdeps/">jdeps</a>. We can run it in our assembled jar to see which dependencies we need.</p>
<pre><code class="language-sh">aiono ❯ jdeps --ignore-missing-deps --list-reduced-deps result/share/java/server-assembly-0.1.0.jar
   java.base
   java.desktop
   java.managementcipher suites 
   java.naming
   java.security.jgss
   java.security.sasl
   java.sql
   jdk.unsupported
</code></pre>
<p>Side note: In my case, I later realized that I needed some more modules for my application to actually work. These were <code>jdk.crypto.ec</code> and <code>jdk.crypto.cryptoki</code>. Without these, I couldn't make requests to some websites which requires encryption algorithms provided from these modules. In case you see <code>javax.net.ssl.SSLHandshakeException: Received fatal alert: insufficient_security</code> adding these may solve your issues.</p>
<p>Let's add those:</p>
<pre><code class="language-nix">let
    repository = builtins.fetchTarball {
        url = &quot;https://github.com/zaninime/sbt-derivation/archive/master.tar.gz&quot;;
    };
    sbt-derivation = import &quot;${repository}/overlay.nix&quot;;
    app = let
        # Define custom JRE 👇
        jre = pkgs.jre_minimal.override {
            modules = [
                &quot;java.base&quot;
                &quot;java.desktop&quot;
                &quot;java.logging&quot;
                &quot;java.management&quot;
                &quot;java.naming&quot;
                &quot;java.security.jgss&quot;
                &quot;java.security.sasl&quot;
                &quot;java.sql&quot;
                &quot;java.transaction.xa&quot;
                &quot;java.xml&quot;
                &quot;jdk.unsupported&quot;
            ];
        };
    in
    sbt-derivation.mkSbtDerivation.${system} {
        # ...

        installPhase = ''
        mkdir -p $out/bin
        mkdir -p $out/share/java

        cp src/app/target/scala-3.*/*.jar $out/share/java

        # Use custom JRE 👇
        makeWrapper ${jre}/bin/java $out/bin/scala-app \
            --add-flags &quot;-cp \&quot;$out/share/java/*\&quot; org.app.Application&quot;
        '';
    };
    in 
    # ...
</code></pre>
<p>Let's build:</p>
<pre><code class="language-sh">aiono ❯ nix-build
aiono ❯ cat result | docker load
aiono ❯ docker images
REPOSITORY                        TAG                               IMAGE ID      CREATED        SIZE
localhost/app-container           latest                            54a30dcb4708  55 years ago   596 MB
</code></pre>
<p>Got a bit bigger, but hopefully at least it runs:</p>
<pre><code class="language-sh">aiono ❯ docker run --expose 4041 --network host localhost/app-container:latest
Server is listening at '/127.0.0.1:4041'
</code></pre>
<p>Yes! It successfully runs now.</p>
<p>While we made some progress, still it's far away from an appropriate size. The bulk of the size still comes from the JRE.</p>
<p>So I kept reading around, I noticed that I didn't notice an important part of the <code>jre_minimal</code> derivation. While I optimized the modules we need, I didn't pick an appropriate JDK. We can override the <code>jdk</code> attribute of the derivation for that.</p>
<pre><code class="language-nix">let
    repository = builtins.fetchTarball {
        url = &quot;https://github.com/zaninime/sbt-derivation/archive/master.tar.gz&quot;;
    };
    sbt-derivation = import &quot;${repository}/overlay.nix&quot;;
    app = let
        jre = pkgs.jre_minimal.override {
            modules = [
                &quot;java.base&quot;
                &quot;java.desktop&quot;
                &quot;java.logging&quot;
                &quot;java.management&quot;
                &quot;java.naming&quot;
                &quot;java.security.jgss&quot;
                &quot;java.security.sasl&quot;
                &quot;java.sql&quot;
                &quot;java.transaction.xa&quot;
                &quot;java.xml&quot;jdk to J
                &quot;jdk.unsupported&quot;
            ];
            # Set JDK to headless 👇
            jdk = pkgs.jdk21_headless;
        };
    in
    ...
</code></pre>
<p>Let's build our image again:</p>
<pre><code class="language-sh">aiono ❯ nix-build
aiono ❯ cat result | docker load
aiono ❯ docker images
REPOSITORY                        TAG                               IMAGE ID      CREATED        SIZE
localhost/app-container           latest                            ed070c56e715  55 years ago   239 MB
</code></pre>
<p>239 MB! That seems promising. Let's run and hopefully it doesn't crash:</p>
<pre><code class="language-sh">aiono ❯ docker run --expose 4041 --network host localhost/app-container:latest
Server is listening at '/127.0.0.1:4041'
</code></pre>
<p>Great! We have come a long way from 722 MB to 239 MB.</p>
<h2><code>dockerTools.copyToRoot</code> Gotchas</h2>
<p>I wrote before that the application JAR is duplicated. It appears both under <code>/share</code> and <code>/nix/store</code> paths in the file system of the container. Let's look into how we defined our container image:</p>
<pre><code class="language-nix">let
    # ...
    app-container = pkgs.dockerTools.buildImage {
        name = &quot;app-container&quot;;
        tag = &quot;latest&quot;;

        # What does it do 🤔
        copyToRoot = [ app pkgs.busybox ];

        config = { Cmd = [ &quot;/bin/${app.pname}&quot; ]; };
    };
in
    # ...
</code></pre>
<p>We duplicated the JAR because we tell Nix to copy the contents of the packages given in <code>copyToRoot</code> to the root of the container image. What we actually want to do is to put <em>symlinks</em> to the root that point to <code>/nix/store</code> because everything we need is already there.</p>
<p>The following change fixes the problem:</p>
<pre><code class="language-nix">let
    # ...
    app-container = pkgs.dockerTools.buildImage {
        name = &quot;app-container&quot;;
        tag = &quot;latest&quot;;

        # Use buildEnv 👇
        copyToRoot = pkgs.buildEnv {
            name = &quot;image-root&quot;;
            paths = [ app pkgs.busybox ];
            pathsToLink = [ &quot;/bin&quot; ];
        };

        config = { Cmd = [ &quot;/bin/${app.pname}&quot; ]; };
    };
in
    # ...
</code></pre>
<p>There are two changes we made here:</p>
<ol>
<li>We wrapped the packages with <code>pkgs.buildEnv</code> which allows us to generate symlinks to <code>/nix/store</code> via the <code>pathsToLink</code> attribute.</li>
<li>We only included <code>/bin</code> in <code>pathsToLink</code> so everything else (such as <code>/share</code>) won't be put to the root of the image from the packages.</li>
</ol>
<p>For some reason, <code>pkgs.buildEnv</code> is heavily underdocumented. The best documentation I could find was <a href="https://nixos.org/manual/nixpkgs/stable/#sec-building-environment">here</a> which doesn't even exclusively talk about <code>buildEnv</code>. But essentially it's a simpler version of <code>mkShell</code>. Using it we can create an environment with the packages we want. In our case the important part is that it allows us to generate symlinks to the actual content in the <code>/nix/store</code>.</p>
<p>Again, let's build the image to see its size:</p>
<pre><code class="language-sh">aiono ❯ nix-build
aiono ❯ cat result | docker load
aiono ❯ docker images
REPOSITORY                        TAG                               IMAGE ID      CREATED        SIZE
localhost/app-container           latest                            5cfef9f22c2a  55 years ago   198 MB
</code></pre>
<p>We got down to 198 MB from 239 MB.</p>
<h2>Comparison with other approaches</h2>
<p>After I had something I considered acceptable, I wanted to compare it with other approaches. By following <a href="https://medium.com/jeroen-rosenberg/lightweight-docker-containers-for-scala-apps-11b99cf1a666">this article</a> I was able to build an image with size of 123 MB, which is better than 198 MB but I think it's not that bad. Also, with this approach the filesystem is a lot more cluttered and there are many unwanted binaries lying in <code>/bin</code>. Apart from the size these can cause complications. But this approach shows that there are still things I could possibly improve.</p>
<p>Compared to other articles like https://zendesk.engineering/using-nix-to-develop-and-package-a-scala-project-cadccd56ad06 (568 MB) and https://dev.to/fialhorenato/how-to-create-slim-docker-java-images-using-a-minimal-jre-3a20 (349 MB) this approach leads to much smaller images.</p>
<h2>Final derivation</h2>
<p>In the end, we have the following derivation:</p>
<pre><code class="language-nix">    let 
    repository = builtins.fetchTarball {
        url = &quot;https://github.com/zaninime/sbt-derivation/archive/master.tar.gz&quot;;
    };
    sbt-derivation = import &quot;${repository}/overlay.nix&quot;;
    app = let
        jre = pkgs.jre_minimal.override {
            # NOTE: What you need to put here depends on your application dependencies
            modules = [
                &quot;java.base&quot;
                &quot;java.desktop&quot;
                &quot;java.logging&quot;
                &quot;java.management&quot;
                &quot;java.naming&quot;
                &quot;java.security.jgss&quot;
                &quot;java.security.sasl&quot;
                &quot;java.sql&quot;
                &quot;java.transaction.xa&quot;
                &quot;java.xml&quot;
                &quot;jdk.unsupported&quot;
                # These modules are necessary for establishing SSL connections.
                # Otherwise I get &quot;javax.net.ssl.SSLHandshakeException: Received fatal alert: insufficient_security&quot;
                &quot;jdk.crypto.ec&quot;
                &quot;jdk.crypto.cryptoki&quot;
            ];
            jdk = pkgs.jdk21_headless;
        }; in 
        sbt-derivation.mkSbtDerivation.${system} {
            pname = &quot;app&quot;;
            version = &quot;0.0.1&quot;;
            src = ./.;
            depsSha256 = &quot;sha256-06Qog8DyDgisnBhUQ9wW46WqqnhGXlakI1DSuFHkriQ=&quot;;

            buildInputs = with pkgs; [ makeWrapper ];

            buildPhase = &quot;sbt assembly&quot;;

            installPhase = ''
            mkdir -p $out/bin
            mkdir -p $out/share/java

            cp src/app/target/scala-3.*/*.jar $out/share/java

            makeWrapper ${jre}/bin/java $out/bin/scala-app \
                --set JAVA_HOME ${jre} \
                --add-flags &quot;-cp \&quot;$out/share/java/*\&quot; org.app.Application&quot;
            '';
        };
    app-container = pkgs.dockerTools.buildImage {
        name = &quot;app-container&quot;;
        tag = &quot;latest&quot;;

        copyToRoot = pkgs.buildEnv {
            name = &quot;image-root&quot;;
            paths = [ app pkgs.busybox ];
            pathsToLink = [ &quot;/bin&quot; ];
        };

        config = { Cmd = [ &quot;/bin/${app.pname}&quot; ]; };
    }; in
    app-container
</code></pre>
<h2>Conclusion</h2>
<p>In conclusion, I was able to reduce the container size from 722 MB to 198 MB with the changes I mentioned. Thanks to Nix, creating a minimal image is really convenient because the build system does the most of the heavy lifting to figure out the necessary packages. Java also has very good tooling to create a minimal JRE, just enough for the application to run. I believe there is still room for improvement to reduce the size, but it is already good enough for my use case.</p>
<p>With modern tools and workflows we easily forget how much waste we produce because most of the time it's not noticeable unless you are looking for it. Some of the waste makes sense, memory and CPU are not the only resources we have, developer time and time to implement new changes are also very valuable resources which a lot of the times more important than hardware resources. But still I think it's worthwhile to spend some time for reducing the waste and inefficiency in our software. These times are opportunities to learn new things, and also it can be helpful for other resources along with hardware efficiency.</p>
]]></description>
      </item>
      
      <item>
         <title>Why Every Programming Language Sucks at Error Handling - Part 1</title>
         <link>https://blog.aiono.dev/posts/why-every-programming-language-sucks-at-error-handling---part-1.html</link>
         <guid>https://blog.aiono.dev/posts/why-every-programming-language-sucks-at-error-handling---part-1.html</guid>
         <pubDate>8 Mar 2025 00:00:00 GMT</pubDate>
         <description><![CDATA[<p>Proper error handling is hard. It's a concept since the inception of programming, yet solutions we have are hardly satisfying. When one tries to add proper error handling into their code it gets much more complicated than just implementing the happy path. Part of it is inescapable because it's <a href="https://en.wikipedia.org/wiki/No_Silver_Bullet">essential complexity</a>, but part of the complication is not necessary hence <a href="https://en.wikipedia.org/wiki/No_Silver_Bullet">accidental complexity</a>. A lot of production code either ignores errors or simply accumulate them to the top level with a generic error message. It's very rare that all possible error cases are even considered before implementation, let alone reflected in the code.</p>
<p>Strangely, mainstream languages still suck at providing tools for good error handling. The main issues I see are:</p>
<ul>
<li>Not differentiating between bugs and recoverable errors (more on that later).</li>
<li>Increasing the <a href="https://minds.md/zakirullin/cognitive">cognitive load</a> more than necessary. It's caused by not leveraging type system for error handling.</li>
<li>Poor composability of functions that can raise an error. Especially when you want to chain multiple functions that return different set of errors. This is usually because type system is too restrictive.</li>
</ul>
<p>In this article series, I will try to describe what &quot;error&quot; really means in programming context. Then I will go about mainstream approaches to error handling, argue about their strengths and weaknesses. I won't say anything novel or groundbreaking. I don't have answers on the best error handling approach. It will be more like documentation of what we know about error handling. It's also to help myself to organize my thoughts about error handling.</p>
<h2>Bugs and Recoverable Errors</h2>
<p>When we say &quot;error&quot;, we actually possibly mean one of the two things:</p>
<ol>
<li>A bug in the system.</li>
<li>A faulty situation that can't be avoided.</li>
</ol>
<p>These two things are <a href="https://joeduffyblog.com/2016/02/07/the-error-model/#bugs-arent-recoverable-errors">fundamentally different</a>. However, many programming languages don't make a clear differentiation between the two when they are designing their error model. This causes complications because they are very different in nature. When one tries to deal with two very different things with the same tools, the tool becomes unnecessarily complex. Instead, it would be better to build two separate tools that solve each of them well.</p>
<p>Bugs cause your system to go into an unanticipated state. Because you didn't actually think that this case would happen in reality and your whole design is based on that assumption. Naturally, this means that all your previous assumptions are not true anymore. Invariants may be violated or state may be corrupted.</p>
<p>A recoverable error, on the other hand, are the things that may happen, and usually it's outside the control of the system. The system has to interact with the outside world and outside world is a lot of the times unpredictable. Therefore, the system should have a way to handle these errors. A download manage should retry when a network error occurs, a text editor should not crash if it  fails to save.</p>
<p>If the system continues to operate in the face of a bug, it will likely to start behaving in weird ways. There is no proper way to &quot;handle&quot; a bug other than immediately stopping and reporting it. That's why &quot;did you turn it on and off again?&quot; is such a popular thing with computer. By restarting a system, we reset the state and it goes back to a case which doesn't break our assumptions about the system. We didn't actually solve the issue, it's still possible to go into the undesired state.</p>
<p>But you can be saying: &quot;If I kill my application every time there was a division by zero or null pointer dereference, it would restart every second&quot;. I don't imply that you should restart all your system when a bug occurs. But you should reset <em>all the affected components</em>. If two threads share a memory region and one encounters a bug, the second is also potentially buggy. You need to reset both. We have problems with restarting only because we can't easily restart a granular part of a system. If components are properly isolated, restarting a process in the face of a bug doesn't bring down the whole system, which prevents cascading failures in the downstream. You end up with a more reliable system. Also, one of the issues with just continuing in the face of a bug, it makes it <em>easier not to fix the bug than to fix the bug</em>. This leads to a <a href="https://blog.codinghorror.com/falling-into-the-pit-of-success/">pit of despair</a> where you ironically end up with a less reliable system that always runs but not it's not certain that it's actually in a well-defined state.</p>
<p>I believe Java especially made significant harms into the understanding of error handling because they don't clearly separate bugs and recoverable errors. A <code>NullPointerException</code> is handled the same way an <code>IOException</code> is handled. This blurs the line and makes it harder to notice the fundamental differences. Also, Java doesn't provide a functionality like Rust's <code>unwrap</code> to ignore error cases for dirty hacking. Which makes people to hate checked exceptions.</p>
<p>When we try to deal with both bugs and recoverable errors in the same way, error handling sucks more than necessary.</p>
<h2>The Philosophy of Error Handling</h2>
<p>As far as I know, there are two main philosophical approaches to error handling. First, one acknowledges that error handling is too hard and complex to manage it in a type system. Therefore, they either use special error values or runtime exceptions. There are further divisions in this camp (like Go's approach vs. unchecked exception approach) but I won't go into detail about them. All of those subcamps has the same belief that it's not worth encoding errors into the types.</p>
<p>The other camp believes that error handling is complex, but it can be somewhat tracked in the type system even if it's not possible for every case. They think that many trivial errors (like famous <code>NullPointerException</code>) could be prevented with encoding more information into type system. Examples to this camp are Rust, OCaml and Haskell. Scala is also in this camp but it's possible to code in all possible ways with Scala.</p>
<p>Both camps have their advantages and shortcomings. First approach is good when you want to focus on the happy path and reliability is not that important. Many times we want to write a hacky prototype before we actually want to write a proper system. We can save a lot of time by just throwing the ball (such as terminating the program or whatever appropriate unit of execution) when a failure case occurs. But when it comes to seriously building a system, it shouldn't be hard to become stricter with error handling. It should be easy to spot where we are not dealing with a possible error so that we can build reliable and robust systems. That's why I especially find Rust's approach really good. Because if you are doing hacky prototype, you can just <code>unwrap</code> errors and let the program panic if an error happens. When you return, you can rewrite your code to handle these errors. It's very easy to figure out where you should look. Compared to this if you use Java for instance, any function can throw a runtime exception and there is no way of telling it from the signature. In order to be sure you have to read the whole implementation of the function. This is terrible because it causes unnecessary <a href="https://minds.md/zakirullin/cognitive">cognitive load</a>.</p>
<p>Second approach is good that it lets you abstract over failure modes by incorporating into type system. This way you can just look at the signature and not have to read the implementation to see what errors are possible. It provides better abstraction and reduces <a href="https://minds.md/zakirullin/cognitive">cognitive load</a>. However, in the way it's currently implemented in programming languages it's usually painful to write. Particularly, it's hard or impossible to compose error cases from multiple functions or narrow cases by partially handling some of the errors. So composability suffers. I will talk about this in the next article when we discuss monadic errors.</p>
<h2>Conclusion</h2>
<p>We reached the end of the first part in this series. I hope it helped you to understand the purpose of error handling and different approaches to it. In the next article, I will delve on different types of error handling models of mainstream programming languages and some cutting-edge ones. We will learn pros and cons of each approach.</p>
]]></description>
      </item>
      
      <item>
         <title>Pinning Nixpkgs without Channels</title>
         <link>https://blog.aiono.dev/posts/pinning-nixpkgs-without-channels.html</link>
         <guid>https://blog.aiono.dev/posts/pinning-nixpkgs-without-channels.html</guid>
         <pubDate>20 Oct 2024 00:00:00 GMT</pubDate>
         <description><![CDATA[<p><a href="https://zero-to-nix.com/concepts/channels">Nix Channels</a> is probably one of the most controversial parts of the <a href="https://wiki.nixos.org/wiki/Nix_package_manager">Nix</a>. While Nix claims to be fully reproducible, Nix <a href="https://wiki.nixos.org/wiki/Derivations">derivations</a> implicitly refer to a <a href="https://wiki.nixos.org/wiki/Nixpkgs">nixpkgs</a> usually through a channel. This breaks the reproducibility promise because the version of nixpkgs depends on the environment that the derivation is built.</p>
<p>One popular alternative for the traditional Nix derivations is to use flakes. There are even efforts to <a href="https://discourse.nixos.org/t/an-incremental-strategy-for-stabilizing-flakes/16323">stabilize them for a long time</a>. However, they are a whole new approach and require some learning for a traditional Nix user. Moreover, they are still experimental so the API is subject to change in the future.</p>
<p>In this post I will show how you can get rid of channels but still use the traditional nix derivations and pin your nixpkgs for your derivations, <a href="https://wiki.nixos.org/wiki/Development_environment_with_nix-shell">shells</a>, <a href="https://nix.dev/tutorials/nix-language.html#nixos-configuration">NixOS configuration</a> and <a href="https://nix-community.github.io/home-manager/index.xhtml#sec-usage-configuration">home manager configurations</a>. In the end you will end up with a setting where nixpkgs version is managed via plain text and can easily be updated when desired.</p>
<h2>The Problem</h2>
<p>Conventional nix shells contain code snippet similar to the following:</p>
<pre><code class="language-nix">let pkgs = import &lt;nixpkgs&gt; {}; in
pkgs.mkShell {
  # ...
}
</code></pre>
<p>You may wonder, what does <code>&lt;nixpkgs&gt;</code> mean in this code?. This syntax is called &quot;lookup path&quot;<sup><a href="#fn-1" id="ref-1-fn-1" role="doc-noteref" class="fn-label">[1]</a></sup>. When you write a name in angle brackets, it's matched with the corresponding key-value pair in <code>NIX_PATH</code> environment variable. The value is typically a Nix channel.</p>
<p>Nix Channels are essentially URLs that point to a nixpkgs<sup><a href="#fn-2" id="ref-1-fn-2" role="doc-noteref" class="fn-label">[2]</a></sup>. Conventionally there are certain channels which are listed <a href="https://status.nixos.org/">here</a>. The exact contents of a channel are updated regularly. So they act like package indices which can be found in other traditional package managers. Having a package index has several benefits. First, they allow conveniently updating all installed packages like one does in traditional package managers. Furthermore, having a global version of a dependency is also beneficial for caching purposes, because packages we use that may depend on the same package depend on the same version, so we don't end up with many versions of the dependency with slight differences.</p>
<p>But of course it's not all good. First problem is, having a global version for every dependency makes it hard if we really want multiple different versions of the same package. For example it's not uncommon that one wants multiple versions of JDK installed at the same time. For this, nixpkgs have conventions that exposes several different major versions (for example JDK has many versions such as <a href="https://search.nixos.org/packages?channel=unstable&amp;show=jdk8&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=jdk">jdk8</a> and <a href="https://search.nixos.org/packages?channel=unstable&amp;show=jdk17&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=jdk">jdk17</a>) which solves this issue for many cases but it's still sometimes not enough. Second problem is exact version of the nixpkgs is not specified in the Nix derivation. If someone tries to build your Nix Derivation couple years later it may not build because the channel is updated with breaking changes. This is a really bad UX, because a channel url says nothing about actual version of nixpkgs being returned. For example, if <code>NIX_PATH</code> environment variable is set to <code>nixpkgs-unstable=nixos-24.05</code>, <code>&lt;nixpkgs-unstable&gt;</code> will refer to the NixOS 24.05 stable branch! This can be very unintuitive. Or worse, many use <code>&lt;nixpkgs&gt;</code> channel for their default channel but some set it to a stable and others to an unstable channel. Reader has no idea which version <code>&lt;nixpkgs&gt;</code> is meant to refer. Also, the exact contents of the channel url changes over time which means that the derivation may get broken in the future.</p>
<p>We can't really fix the first problem with the traditional Nix. I believe that it's an inherent trade-off between space usage and preciseness. But fortunately we can solve the second problem. Therefore ensuring reproducibility and improving the UX.</p>
<p>In order to fix this issue, we have the following options:</p>
<ol>
<li>Don't use <code>&lt;nixpkgs&gt;</code> expressions in the code.</li>
<li>Keep <code>&lt;nixpkgs&gt;</code> but change <code>NIX_PATH</code> variable to refer to a specific version of nixpkgs instead of a channel.</li>
</ol>
<p>We will use both options depending on the use case. But first we need to introduce a new tool.</p>
<h2>First Attempt at a Solution</h2>
<p>As I have described above, the problematic line is:</p>
<pre><code class="language-nix">let pkgs = import &lt;nixpkgs&gt; {}; in
</code></pre>
<p>We already know that <code>&lt;nixpkgs&gt;</code> is supposed return a version of nixpkgs. So one simple solution could be to download a specific commit of nixpkgs instead of using channels via lookup paths. Is this possible?</p>
<p>Yes, fortunately it is possible. The nixpkgs repository is <a href="https://github.com/NixOS/nixpkgs">hosted at GitHub</a>. GitHub has a nice feature that allowing <a href="https://docs.github.com/en/repositories/working-with-files/using-files/downloading-source-code-archives#source-code-archive-urls">downloading the source archive of any commit</a>. Nix has a builtin called <a href="https://nix.dev/manual/nix/2.18/language/builtins.html?#builtins-fetchTarball">fetchTarball</a> which, as the name suggests, downloads the tarball and returns it's Nix store path. With this knowledge, we can instead write:</p>
<pre><code class="language-nix">let pkgs = import (fetchTarball &quot;https://github.com/NixOS/nixpkgs/archive/$COMMIT_HASH.tar.gz&quot;) {}; in
</code></pre>
<p>This solves the reproducibility issue. So can we know stop and be happy?</p>
<p>Unfortunately not, we have achieved perfect reproducibility. However, how do we update nixpkgs version if we want to? Our current option is to go to nixpkgs repository and pick the latest commit then copy-paste it. But if we have a lot of shell configurations this can easily get very tedious. Can we automate the process a bit?</p>
<p>Well we can. Enter npins.</p>
<h2>Npins</h2>
<p><a href="https://github.com/andir/npins">Npins</a> is a tool that allows &quot;pinning&quot; a specific commit of the nixpkgs in nix derivations. The version is stored in a text file, therefore you can easily add it to version control. It also lets you conveniently update the version when you want, no manual text editing is required. It's <a href="https://search.nixos.org/packages?channel=24.05&amp;show=npins&amp;from=0&amp;size=50&amp;sort=relevance&amp;type=packages&amp;query=npins">available in nixpkgs</a>.</p>
<p>Once you have it installed, you need to initialize it in the directory you want to use:</p>
<pre><code class="language-bash">npins init --bare
</code></pre>
<p>This will create <code>npins</code> subdirectory on the current directory. Initially there are no pinned nixpkgs and it needs to be added with another command.</p>
<p>Adding nixpkgs can be done with:</p>
<pre><code class="language-bash">npins add github nixos $NIXPKGS_NAME --branch $NIXPKGS_BRANCH
</code></pre>
<p>Where <code>$NIXPKGS_BRANCH</code> can be a <a href="https://wiki.nixos.org/wiki/Channel_branches">Nix Channel</a> name. <code>$NIXPKGS_NAME</code> is the name of nixpkgs. This is necessary because npins lets you pin multiple nixpkgs in the same repository.</p>
<p>After having nixpkgs pinned, it can be used in nix derivations as following:</p>
<pre><code class="language-nix">{
  system ? builtins.currentSystem,
  sources ? import ./npins,
}:
let
  pkgs = import sources.nixpkgs { inherit system; };
in
...
</code></pre>
<p><code>sources</code> contains the pinned nixpkgs, the name you gave above becomes an attribute in <code>sources</code> to access. So if you have the name <code>foo</code>, you would get nixpks by <code>import sources.foo {}</code>.</p>
<h2>Packaging &amp; Shell</h2>
<p>As shown above, nix shell configurations utilize <code>&lt;nixpkgs&gt;</code> syntax, which is the main deal breaker for reproducibility. Therefore the solution is to import nixpkgs from <code>sources</code> provided via npins.</p>
<p>I use the following template:</p>
<ul>
<li><code>nix/shell.nix</code> : Contains the actual shell configuration which should be built with <a href="https://nix.dev/tutorials/callpackage.html">callPackage</a>. Example:</li>
</ul>
<pre><code class="language-nix">{
  mkShell,
  # Other dependencies...
}:

mkShell {
  # Shell configuration...
}
</code></pre>
<ul>
<li><code>shell.nix</code> : Calls <code>nix/shell.nix</code> with the pinned nixpkgs. For example if we have nixpkgs with name <code>nixpkgs</code>:</li>
</ul>
<pre><code class="language-nix">{
  system ? builtins.currentSystem,
  sources ? import ./npins,
}:
let
  pkgs = import sources.nixpkgs { inherit system; };
in
pkgs.callPackage ./nix/shell.nix {}
</code></pre>
<h3>Bonus: direnv</h3>
<p>For convenience I use <a href="https://direnv.net/">direnv</a> to enter <code>nix-shell</code>. It's convenient because it automatically enters into the environment and it's for some reason faster than calling <code>nix-shell</code> directly.</p>
<p>With <code>direnv</code> installed, I have the following <code>.envrc</code> file:</p>
<pre><code class="language-direnv">use nix
</code></pre>
<p>Which is all we need.</p>
<h2>NixOS configuration</h2>
<p>It's harder to pin nixpkgs for NixOS configurations, because NixOS configurations implicitly depend on <code>&lt;nixpkgs/nixos&gt;</code>, and because it's a lookup path, it requires <code>NIX_PATH</code> environment variable to point to <code>nixpkgs</code>. It kind of creates a loop if you want to declare <code>NIX_PATH</code> inside <code>configuration.nix</code>, because in order to interpret <code>configuration.nix</code> you first need to determine the location of <code>nixpkgs</code> which is only available after <code>NIX_PATH</code> is set. So this way of managing requires to run <code>nixos-rebuild</code> <strong>twice</strong> to actually take effect. I think this is not a good UX. Fortunately, we can write a script that passes the appropriate <code>NIX_PATH</code> to <code>nixos-rebuild</code>. In order to prevent incorrect usage, <code>NIX_PATH</code> shouldn't be empty by default.</p>
<p>I use the following <a href="https://www.nushell.sh/">Nu Shell</a> script for this:</p>
<pre><code class="language-nu">def build-nixos-configuration [
  device_path: string, # Path for the system configuration. Must contain a 'configuration.nix' and an 'npins' directory.
  command: string = &quot;switch&quot;, # Main command for 'nixos-rebuild'. 'switch' or 'dry-run'
  ...extra_args: string, # Passed to 'nixos-rebuild'
]: nothing -&gt; nothing {
  let abs_device_path = ($device_path | path expand --strict);

  let npins_path = ($abs_device_path | path join npins default.nix);
  let nixpkgs_pin = run-external &quot;nix&quot; &quot;eval&quot; &quot;--raw&quot; &quot;-f&quot; $npins_path &quot;nixpkgs&quot;;

  let configuration_path = ($abs_device_path | path join configuration.nix);

  let nix_path = $&quot;nixpkgs=($nixpkgs_pin):nixos-config=($configuration_path)&quot;;
  with-env {
    NIX_PATH: $nix_path,
  } {
    # For some reason '--preserve-env=NIX_PATH' doesn't pass the env variable.
    sudo &quot;--preserve-env&quot; &quot;-u&quot; $&quot;(whoami)&quot; &quot;nixos-rebuild&quot; $command &quot;--fast&quot; ...$extra_args
  }
}
</code></pre>
<p>It may seem complicated, but the essential part is very simple:</p>
<pre><code class="language-nu">let nixpkgs_pin = run-external &quot;nix&quot; &quot;eval&quot; &quot;--raw&quot; &quot;-f&quot; $npins_path &quot;nixpkgs&quot;;
let nix_path = $&quot;nixpkgs=($nixpkgs_pin):nixos-config=($configuration_path)&quot;;
with-env {
  NIX_PATH: $nix_path,
} {
  sudo &quot;--preserve-env&quot; &quot;-u&quot; $&quot;(whoami)&quot; &quot;nixos-rebuild&quot; $command &quot;--fast&quot; ...$extra_args
}
</code></pre>
<ol>
<li>First line evaluates the nixpkgs and returns a <a href="https://wiki.nixos.org/wiki/Nix_package_manager#Nix_store">Nix Store</a> path.</li>
<li>Second line creates the appropriate <code>NIX_PATH</code> env variables. <code>nixpkgs</code> is a path to nixpkgs and <code>nixos-config</code> is the path to <code>configuration.nix</code>.</li>
<li>Sixth line calls <code>nixos-rebuild</code> with appropriate arguments.</li>
</ol>
<p>The same part can be written in bash as (disclaimer: I didn't test it):</p>
<pre><code class="language-bash">NIXPKGS_PIN=$(nix eval --raw -f $NPINS_PATH nixpkgs)
NIX_PATH=&quot;nixpkgs=$NIXPKGS_PIN:nixos-config=$CONFIGURATION_PATH&quot;
sudo --preserve-env -u &quot;$(whoami)&quot; nixos-rebuild $command --fast $@
</code></pre>
<p>With a script like this, one can pin nixpkgs for their system configuration.</p>
<h2>Home Manager</h2>
<p>If you use <a href="https://nix-community.github.io/home-manager/">Home Manager</a> the approach is very close to the system configuration. It's essentially the same, except in <code>NIX_PATH</code> you don't need to set <code>nixos-config</code> but you need <code>home-manager</code>:</p>
<pre><code class="language-bash">NIXPKGS_PIN=$(nix eval --raw -f $NPINS_PATH nixpkgs)
HOME_MANAGER_PIN=$(nix eval --raw -f $NPINS_PATH home-manager)
NIX_PATH=&quot;nixpkgs=$NIXPKGS_PIN:home-manager=$HOME_MANAGER_PIN&quot;
home-manager $command -f $HOME_MANAGER_PATH
</code></pre>
<h2>Conlusion</h2>
<p>If you have come this far, thank you for giving your time. I believe that traditional Nix does many things right, but the way nixpkgs is managed really needs to change. With pinning nixpkgs using <code>npins</code> you can improve this one specific issue.</p>
<p>Credits: This post is heavily inspired by <a href="https://jade.fyi/blog/pinning-nixos-with-npins/">https://jade.fyi/blog/pinning-nixos-with-npins/</a>. If you want more deep dive explanation on the same topic I would recommend it.</p>
<section role="doc-endnotes"><ol>
<li id="fn-1">
<p><a href="https://nix.dev/tutorials/nix-language#lookup-path-tutorial">https://nix.dev/tutorials/nix-language#lookup-path-tutorial</a></p>
<span><a href="#ref-1-fn-1" role="doc-backlink" class="fn-label">↩︎︎</a></span></li><li id="fn-2">
<p><a href="https://zero-to-nix.com/concepts/channels">https://zero-to-nix.com/concepts/channels</a></p>
<span><a href="#ref-1-fn-2" role="doc-backlink" class="fn-label">↩︎︎</a></span></li></ol></section>
]]></description>
      </item>
      
      <item>
         <title>Writing My Own Static Site Generator in OCaml</title>
         <link>https://blog.aiono.dev/posts/writing-my-own-static-site-generator-in-ocaml.html</link>
         <guid>https://blog.aiono.dev/posts/writing-my-own-static-site-generator-in-ocaml.html</guid>
         <pubDate>20 Sep 2024 00:00:00 GMT</pubDate>
         <description><![CDATA[<p>Hello world! This is my first blog post.</p>
<p>For a while, I wanted to start blogging about programming, and writing my own blog engine seemed like a good idea to keep it interesting.
While keeping me interested in working as intended, I think I could have published my first post much earlier if I used an existing blog engine. But it's like scripting, doing a task manually sometimes shorter than writing a script for it, but programmers still choose scripting. Also, I like the idea of being familiar with every stack of my projects. Nowadays, it's rare to work without frameworks or libraries that abstract over the basic primitives (not that this is a bad thing), especially in the web development setting. I thought it would be interesting if I learned web frontend fundamentals from scratch. Thanks to <a href="https://www.rugu.dev">Uğur</a> for helping me with HTML/CSS.</p>
<p>If you would like to check out the source code, the repository is <a href="https://github.com/onsah/my-static-site-generator">here</a>.</p>
<h2>Why OCaml?</h2>
<p>I am aware that there are a lot of blog posts about writing a blog engine, but I didn't see anyone using OCaml.
I chose OCaml because it seems close to the ideal language in my mind. I really like Rust, and OCaml is similar to Rust in many aspects such as having good support for functional programming while allowing to escape into imperative style when necessary.
But OCaml has a garbage collector instead of lifetime analysis so it's more convenient to use if you don't need to control memory management. And it has some interesting features like <a href="https://ocaml.org/docs/modules">modules</a>, <a href="https://dev.realworldocaml.org/gadts.html">GADT</a>s and <a href="https://ocaml.org/manual/5.2/polyvariant.html">polymorphic variants</a>. However, I miss the guarantees provided by the ownership system.
I had some bugs while working on this project that would be caught by the borrow checker if it existed.
Promisingly, <a href="https://blog.janestreet.com/oxidizing-ocaml-ownership/">there is an ongoing work</a> to implement some sort of borrow checking for OCaml.</p>
<p>Another good thing about OCaml is that its similar to <a href="https://go.dev/">Go</a> in the sense that it compiles fast and has a very minimal runtime.
That makes iterations very quick, and also deployment simple since it just requires a single binary.
Furthermore, while it produces very efficient binary executables it still feels like a scripting language thanks to it's type inference.
I could talk about OCaml's features more in detail, but this post is not about that so I will save this to a future article.
I wanted to try the language in a realistic setting to see how this language works in practice.</p>
<h2>Project Structure</h2>
<p><a href="https://github.com/onsah/my-static-site-generator">The project</a> has two main folders:</p>
<ul>
<li><code>content</code>: Blog content. Page templates and blog posts.</li>
<li><code>site-generator</code>: OCaml program that generates the website.</li>
</ul>
<p><code>content</code> folder contains three subfolders:</p>
<ul>
<li><code>templates</code>: HTML templates for components and pages.</li>
<li><code>pages</code>: The content for pages such as 'about me' and blog posts. For each post, there is a <code>.json</code> file (metadata) and a <code>.md</code> (content) with the same name before the extension.</li>
<li><code>css</code>: As the name implies this contains the css files.</li>
</ul>
<p>The static site generator generates a static HTML website.
Basically, it takes each post's content in markdown form and converts it to HTML.
Then it instantiates the blog post template with the post content.</p>
<h3>Tech Stack</h3>
<p>I use the following tech stack:</p>
<ul>
<li>For the build system I chose <a href="https://dune.build/">dune</a> which is the de-facto build system for OCaml.</li>
<li>Instead of the default standard library, I use <a href="https://opensource.janestreet.com/core/">Core</a> because it contains more functionality and it was used in <a href="https://dev.realworldocaml.org/">Real World OCaml</a> book that I read.</li>
<li>For HTML manipulation I use <a href="https://ocaml.org/p/lambdasoup/latest">Lambda soup</a> library.</li>
<li>The markdown files are converted to HTML using <a href="https://ocaml.org/p/omd/latest">omd</a>.</li>
<li>The post metadata is stored in json files, to parse them I use <a href="https://ocaml.org/p/yojson/latest">yojson</a>. Though I would prefer to have them as <a href="https://jekyllrb.com/docs/front-matter/">YAML Front Matter</a>, I couldn't find a library for it and didn't want to spend time implementing myself. Maybe in the future, I can work on that.</li>
<li>CLI interface is implemented using <a href="https://ocaml.org/p/core_unix/latest/doc/index.html">core_unix</a>.</li>
</ul>
<p>Unfortunately, while OCaml ecosystem is active it's worse than mainstream languages in terms of library support.
You may be disappointed if you expect to find a library for everything.</p>
<h3>Code Structure</h3>
<p>The project is mainly organized by <a href="#modules">Modules</a>.</p>
<ul>
<li><code>main</code>: Programs entry point.</li>
<li><code>DiskIO</code>: Wraps the disk related functionality so the underlying IO library can be changed easily. All actual IO functionality is constrained here.</li>
<li><code>SiteGenerator</code>: Reads the content and generates the website files but doesn't actually write them.</li>
<li><code>SiteDirectory</code>: Writes generated website files into the disk.</li>
</ul>
<h2>Templating</h2>
<p>The templating functionality is very simple, it basically finds the appropriate element with the element id and then replaces it with the actual content. For example, in posts page:</p>
<pre><code class="language-html">&lt;main class=&quot;container&quot;&gt;
    &lt;!-- ... --&gt;
    &lt;div id=&quot;blog-content&quot;&gt;&lt;/p&gt;
&lt;/main&gt;
</code></pre>
<p>The <code>blog-content</code> is substituted with actual content with the following code:</p>
<pre><code class="language-ocaml">let page =
    DiskIO.read_all
        (Path.join content_path (Path.from_parts [ &quot;templates&quot;; &quot;post.html&quot; ]))
    |&gt; Soup.parse
in
Soup.replace (page $ &quot;#blog-content&quot;) post_component;
</code></pre>
<p>Notice that <code>Soup.replace</code> API has side effects, it doesn't return the modified page but updates the target <strong>in place</strong>.
The unexpected thing was, it also mutates <em>the component that is substituted</em> as well, leaving it empty after the operation.
I was expecting that the second argument left unmodified.
It's a good example why borrow checking is useful, if OCaml had borrow checking I would know that the second argument is mutated by looking at the signature and didn't have this surprise.
An immutable API would also prevent such mistakes.</p>
<h2>Modules</h2>
<p>OCaml has a concept called <a href="https://ocaml.org/docs/modules">modules</a> which can be used as a substitute for <a href="https://docs.oracle.com/javase/tutorial/java/IandI/createinterface.html">interfaces</a> in other languages.
I find the module system in general better than classic OO-style interfaces.
One reason is that a module can have multiple related types defined together and not necessarily tied into a single type.
Though for this project, there were no cases where I really needed a module.
All of my modules signatures in the project resembles interfaces.</p>
<h2>Conclusion</h2>
<p>In short, I liked my experience with OCaml though since this project is very simple it wouldn't matter if I used any
other reasonable language.
Unfortunately, some libraries are not well maintained and lack documentation
but tooling is very straightforward and easy to use.
But it's very much an alive ecosystem with a lot of efforts to improve the language.</p>
]]></description>
      </item>
      
   </channel>
</rss>