Jekyll2023-02-27T20:31:05+00:00http://sfietkonstantin.github.io/feed.xmlSfiet_Konstantin’s blogLinux, Sailfish OS, C++, Rust and hacks. Also (bad) takes on software development.The quest for the perfect sum type in Java2021-09-03T00:00:00+00:002021-09-03T00:00:00+00:00http://sfietkonstantin.github.io/2021/09/03/Java-sum-type<p>The goal of this post is to find the perfect sum type implementation in Java.</p>
<p>As a hobbyist Rust developer, I’m used to <a href="https://en.wikipedia.org/wiki/Tagged_union">sum types</a>.
However, I’m very sad that this notion doesn’t exist in our company <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> ‘s language of choice, Java.</p>
<h2 id="why-sum-types-">Why sum types ?</h2>
<p>By quoting Wikipedia</p>
<blockquote>
<p>A tagged union, [or] sum type […] is a data structure used to hold a value that could take on several different,
but fixed, types.</p>
</blockquote>
<p>Instead of “data structure”, I would have said “type”, as a sum type defines a type. Take the Rust code below:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">ProductType</span> <span class="p">{</span>
<span class="n">first</span><span class="p">:</span> <span class="nb">i32</span>
<span class="n">second</span><span class="p">:</span> <span class="nb">String</span>
<span class="p">}</span>
<span class="k">enum</span> <span class="n">SumType</span> <span class="p">{</span>
<span class="nf">First</span><span class="p">(</span><span class="nb">i32</span><span class="p">),</span>
<span class="nf">Second</span><span class="p">(</span><span class="nb">String</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Variables of type <code class="language-plaintext highlighter-rouge">ProductType</code> can hold an int AND a string, while values of type <code class="language-plaintext highlighter-rouge">SumType</code> can hold an int OR
a string.</p>
<p>A sum type is manipulated by checking its variant, usually through pattern matching. This is equivalent of doing
an <code class="language-plaintext highlighter-rouge">instanceof</code> call, but better, as the sum type expresses the types that you are allowed to match.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MakeString</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">makeString</span><span class="o">(</span><span class="nc">Object</span> <span class="n">object</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">object</span> <span class="k">instanceof</span> <span class="nc">String</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">object</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">object</span> <span class="k">instanceof</span> <span class="nc">Integer</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">object</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// Must handle other cases, object could be anything :(</span>
<span class="k">throw</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Only String and int are supported"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="n">StringOrInt</span> <span class="p">{</span>
<span class="nf">String</span><span class="p">(</span><span class="nb">String</span><span class="p">),</span>
<span class="nf">Int</span><span class="p">(</span><span class="nb">i32</span><span class="p">),</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="nf">make_string</span><span class="p">(</span><span class="n">input</span><span class="p">:</span> <span class="n">StringOrInt</span><span class="p">)</span> <span class="k">-></span> <span class="nb">String</span> <span class="p">{</span>
<span class="k">match</span> <span class="n">input</span> <span class="p">{</span>
<span class="nn">StringOrInt</span><span class="p">::</span><span class="nf">String</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=></span> <span class="n">value</span><span class="p">,</span>
<span class="nn">StringOrInt</span><span class="p">::</span><span class="nf">Int</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=></span> <span class="n">value</span><span class="nf">.to_string</span><span class="p">(),</span>
<span class="c">// No need to handle other types or use a wildcard, we have a fixed list of types </span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Being able to hold a value of one type over a predefined set of types offers a lot of expressiveness in the code,
especially when dealing with values <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.</p>
<p>For example, the JSON specification describe data as a tree, where each node can be null, a boolean, a number, a string,
an array or an object. An array contains a list of nodes, and an object is a set key-value pairs, with keys being
strings and values being nodes. A node can be expressed easily with a sum type, and operations on nodes can be written
simply:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="n">JSonNode</span> <span class="p">{</span>
<span class="n">Null</span><span class="p">,</span>
<span class="nf">Bool</span><span class="p">(</span><span class="nb">bool</span><span class="p">),</span>
<span class="nf">Number</span><span class="p">(</span><span class="nb">f64</span><span class="p">),</span>
<span class="nf">String</span><span class="p">(</span><span class="nb">String</span><span class="p">),</span>
<span class="nf">Array</span><span class="p">(</span><span class="nb">Vec</span><span class="o"><</span><span class="n">JSonNode</span><span class="o">></span><span class="p">),</span>
<span class="nf">Object</span><span class="p">(</span><span class="n">HashMap</span><span class="o"><</span><span class="nb">String</span><span class="p">,</span> <span class="n">JSonNode</span><span class="o">></span><span class="p">),</span>
<span class="p">}</span>
<span class="c">// Return the value of the node if it is a string</span>
<span class="k">fn</span> <span class="nf">get_string</span><span class="p">(</span><span class="n">input</span><span class="p">:</span> <span class="n">JSonNode</span><span class="p">)</span> <span class="k">-></span> <span class="nb">Option</span><span class="o"><</span><span class="nb">String</span><span class="o">></span> <span class="p">{</span>
<span class="k">match</span> <span class="n">input</span> <span class="p">{</span>
<span class="nn">JSonNode</span><span class="p">::</span><span class="nf">String</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=></span> <span class="nf">Some</span><span class="p">(</span><span class="n">value</span><span class="p">),</span>
<span class="mi">_</span> <span class="k">=></span> <span class="nb">None</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>For a language without sum types, it can be quickly cumbersome. We might need to rely on inheritance and the visitor
pattern, or do dynamic type checking.</p>
<p>Another strong point of sum types is error handling. By creating sum types with two variants, we can express the
“happy case” and “unhappy case” as two variants. For example, in Rust, a function returning <code class="language-plaintext highlighter-rouge">Option<T></code>
explicitly states that the returned value can be null. To access the underlying <code class="language-plaintext highlighter-rouge">T</code>, the caller must check if
the option is of variant <code class="language-plaintext highlighter-rouge">Some(T)</code>. In Java, where you are unsure if a returned object is null or not, you are
forced to program more defensively, or to inspect the implementation of the function you are calling.</p>
<h2 id="java-progress-on-sum-types">Java progress on sum types</h2>
<p>Java has progressed a lot on supporting sum types the best it can. With Java 17, pattern matching will be in
preview mode. Combined with records and sealed classes, it will provide some kind of sum type.</p>
<p>Records allows reduces boilerplate when dealing with value classes <sup id="fnref:2:1" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. It is also a great addition for code
expressiveness as it allows splitting value classes from service classes.</p>
<p>Sealed classes is the most important component. It allows limiting the number of implementors of an interface.
This brings the “fixed set of types” part of sum types. This already makes switches or <code class="language-plaintext highlighter-rouge">instanceof</code> based checks
exhaustive.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="n">sealed</span> <span class="kd">interface</span> <span class="nc">JSonNode</span>
<span class="n">permits</span> <span class="nc">NullJSonNode</span><span class="o">,</span> <span class="nc">BoolJSonNode</span><span class="o">,</span> <span class="nc">NumberJSonNode</span><span class="o">,</span> <span class="nc">ArrayJSonNode</span><span class="o">,</span> <span class="nc">ObjectJSonNode</span>
<span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">NullJSonNode</span><span class="o">()</span> <span class="kd">implements</span> <span class="nc">JSonNode</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">BoolJSonNode</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">v</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">JSonNode</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">NumberJSonNode</span><span class="o">(</span><span class="kt">double</span> <span class="n">v</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">JSonNode</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">ArrayJSonNode</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">JsonNode</span><span class="o">></span> <span class="n">v</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">JSonNode</span> <span class="o">{}</span>
<span class="kd">public</span> <span class="n">record</span> <span class="nf">ObjectJSonNode</span><span class="o">(</span><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">JsonNode</span><span class="o">></span> <span class="n">v</span><span class="o">)</span> <span class="kd">implements</span> <span class="nc">JSonNode</span> <span class="o">{}</span>
<span class="kd">static</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">visit</span><span class="o">(</span><span class="nc">JSonNode</span> <span class="n">node</span><span class="o">,</span> <span class="nc">NodeVisitor</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">visitor</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">node</span> <span class="k">instanceof</span> <span class="nc">NullJSonNode</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitNull</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">node</span> <span class="k">instanceof</span> <span class="nc">BoolJSonNode</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitBool</span><span class="o">(</span><span class="n">n</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">node</span> <span class="k">instanceof</span> <span class="nc">NumberJSonNode</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitNumber</span><span class="o">(</span><span class="n">n</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">node</span> <span class="k">instanceof</span> <span class="nc">ArrayJSonNode</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitArrayl</span><span class="o">(</span><span class="n">n</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">node</span> <span class="k">instanceof</span> <span class="nc">ObjectJSonNode</span> <span class="n">n</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitObject</span><span class="o">(</span><span class="n">n</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// This chain of ifs is exhaustive, no need for an else</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Finally, pattern-matching <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> will bring more syntactic sugar, allowing code to look similar to the Rust version.</p>
<h2 id="some-constraints">Some constraints</h2>
<p>Now that I have discussed the need of sum types, I will put some constraints on how to design one from scratch.</p>
<p>Indeed, I will not be waiting for Java 17, unreleased at the time this article is written, as I have code to write
today :upside_down_face:. I’m also unwilling to use preview features, to have a bit of stability guarantees. Our exploration will
the be based on:</p>
<ul>
<li>Java 14</li>
<li>No preview features</li>
</ul>
<p>This constraint prevent us from using many interesting features:</p>
<ul>
<li>Records, stabilized in Java 16</li>
<li>Pattern matching for instanceof, stabilized in Java 16</li>
<li>Sealed classes, stabilized in Java 17</li>
<li>Pattern matching for switch, preview in Java 17</li>
</ul>
<p>And our goal is to make the most expressive and convenient sum type. Bonus points will be awarded on how easy it
is to read the code, to maintain and evolve it.</p>
<p>The candidates below will have to implement a 3-variant sum type, containing a string, an int, and a list of variants.
This sum type have be used on two use-cases:</p>
<ul>
<li>Extracting a string from the string variant</li>
<li>Displaying a representation as a string. This applies to any variant</li>
</ul>
<p>For the record, below is a Rust implementation</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span> <span class="n">Type</span> <span class="p">{</span>
<span class="nf">String</span><span class="p">(</span><span class="nb">String</span><span class="p">),</span>
<span class="nf">Int</span><span class="p">(</span><span class="nb">i32</span><span class="p">),</span>
<span class="nf">List</span><span class="p">(</span><span class="nb">Vec</span><span class="o"><</span><span class="n">Type</span><span class="o">></span><span class="p">),</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="nf">get_string</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="n">Type</span><span class="p">)</span> <span class="k">-></span> <span class="nb">Option</span><span class="o"><</span><span class="nb">String</span><span class="o">></span> <span class="p">{</span>
<span class="k">match</span> <span class="n">ty</span> <span class="p">{</span>
<span class="nf">String</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=></span> <span class="nf">Some</span><span class="p">(</span><span class="n">value</span><span class="p">),</span>
<span class="mi">_</span> <span class="k">=></span> <span class="nb">None</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">fn</span> <span class="nf">display</span><span class="p">(</span><span class="n">ty</span><span class="p">:</span> <span class="n">Type</span><span class="p">)</span> <span class="k">-></span> <span class="nb">String</span> <span class="p">{</span>
<span class="k">match</span> <span class="n">ty</span> <span class="p">{</span>
<span class="nf">String</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=></span> <span class="n">value</span><span class="p">,</span>
<span class="nf">Int</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=></span> <span class="n">value</span><span class="nf">.to_string</span><span class="p">(),</span>
<span class="nf">List</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=></span> <span class="p">{</span>
<span class="k">let</span> <span class="n">values</span> <span class="o">=</span> <span class="n">value</span><span class="nf">.into_iter</span><span class="p">()</span><span class="nf">.map</span><span class="p">(</span><span class="n">display</span><span class="p">)</span><span class="py">.collect</span><span class="p">::</span><span class="o"><</span><span class="nb">Vec</span><span class="o"><</span><span class="mi">_</span><span class="o">>></span><span class="p">();</span>
<span class="n">values</span><span class="nf">.join</span><span class="p">(</span><span class="s">", "</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s done in 23 lines.</p>
<h2 id="candidate-1-the-visitor">Candidate 1: the visitor</h2>
<p>Visitors are the most natural way of thinking about a type that can take one value in a limited, heterogeneous
collection of types. In fact, I consider the visitor pattern to be the object-oriented variant of the sum type.</p>
<p>Let’s write our sum type using a visitor, and try to use it.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">accept</span><span class="o">(</span><span class="nc">TypeVisitor</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">visitor</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">interface</span> <span class="nc">TypeVisitor</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="o">{</span>
<span class="no">T</span> <span class="nf">visitString</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">);</span>
<span class="no">T</span> <span class="nf">visitInt</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">);</span>
<span class="no">T</span> <span class="nf">visitList</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">StringType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">value</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">StringType</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">accept</span><span class="o">(</span><span class="nc">TypeVisitor</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">visitor</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitString</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">IntType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">value</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">IntType</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">accept</span><span class="o">(</span><span class="nc">TypeVisitor</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">visitor</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitInt</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">ListType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">ListType</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">accept</span><span class="o">(</span><span class="nc">TypeVisitor</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">visitor</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">visitor</span><span class="o">.</span><span class="na">visitList</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The implementation is 47 lines long, not counting <code class="language-plaintext highlighter-rouge">equals</code> & <code class="language-plaintext highlighter-rouge">hashCode</code>, nor the fact that this code should be in 4
files. It is usually admitted that visitor patterns are <strong>verbose</strong>.</p>
<p>Note that we have implemented a visitor that can return something. It can be incrementally better than the version
returning nothing, especially on readability.</p>
<p>Now let’s use our visitor:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="kd">class</span> <span class="nc">TypeUtils</span> <span class="o">{</span>
<span class="nd">@Nullable</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getString</span><span class="o">(</span><span class="nc">Type</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">type</span><span class="o">.</span><span class="na">accept</span><span class="o">(</span><span class="k">new</span> <span class="nc">TypeVisitor</span><span class="o"><>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">visitString</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">visitInt</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">visitList</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">display</span><span class="o">(</span><span class="nc">Type</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">type</span><span class="o">.</span><span class="na">accept</span><span class="o">(</span><span class="k">new</span> <span class="nc">TypeVisitor</span><span class="o"><>()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">visitString</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">visitInt</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">visitList</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">value</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">TypeUtils:</span><span class="o">:</span><span class="n">display</span><span class="o">)</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">joining</span><span class="o">(</span><span class="s">", "</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>We have another 36 lines.</p>
<p>I have chosen to use inline visitor implementations, but visitors could have been put in different files, as classes.
The advantage of inline visitor is its readability. Here, the two visitors are implementation details, and it’s better
to have them close to the body of the function they belong to.</p>
<p>Note that the first function takes the same space as the second, and requires two dummy implementations for variant
that should not be handled. This can be a bit annoying when we are manipulating a type with many variants, and are only
interested in a single variant.</p>
<p>To fix this, we could think of providing specialized methods, that resolve the variant for us. This bring us
to the candidate 2.</p>
<h2 id="candidate-2-the-encapsulating-interface-v1">Candidate 2: the encapsulating interface (v1)</h2>
<p>As the visitor pattern is verbose, and requires an additional interface that is separated from the type definition,
why not get rid of it ? The goal of this candidate is to put more methods in the interface of <code class="language-plaintext highlighter-rouge">Type</code>, in order
to avoid defining the visitor.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="k">default</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="nf">string</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">empty</span><span class="o">();</span> <span class="o">}</span>
<span class="k">default</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">integer</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">empty</span><span class="o">();</span> <span class="o">}</span>
<span class="k">default</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>></span> <span class="nf">list</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">empty</span><span class="o">();</span> <span class="o">}</span>
<span class="kd">static</span> <span class="nc">Type</span> <span class="nf">string</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">StringType</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">static</span> <span class="nc">Type</span> <span class="nf">integer</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">IntType</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">static</span> <span class="nc">Type</span> <span class="nf">list</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ListType</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">StringType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="n">value</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">StringType</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="nf">string</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">IntType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="kt">int</span> <span class="n">value</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">IntType</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">integer</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">ListType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">ListType</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">value</span> <span class="o">=</span> <span class="n">value</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>></span> <span class="nf">list</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This implementation is (sadly) even longer than the previous one, totaling 55 lines.</p>
<p>However, it has some advantages. By putting the interface and it’s implementors in the same file, we link them together.
This will help the maintainer understand that this inheritance graph is less related to inheritance than to the
definition of a single type.</p>
<p>This implementation also improve the API, exposing a single type to external users. We are only missing the <code class="language-plaintext highlighter-rouge">sealed</code>
keyword to prevent users from implementing the interface.</p>
<p>We can now try to use this second candidate.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">TypeUtils</span> <span class="o">{</span>
<span class="nd">@Nullable</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getString</span><span class="o">(</span><span class="nc">Type</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">type</span><span class="o">.</span><span class="na">string</span><span class="o">().</span><span class="na">orElse</span><span class="o">(</span><span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">display</span><span class="o">(</span><span class="nc">Type</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">type</span><span class="o">.</span><span class="na">string</span><span class="o">().</span><span class="na">isPresent</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">type</span><span class="o">.</span><span class="na">string</span><span class="o">().</span><span class="na">get</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">type</span><span class="o">.</span><span class="na">integer</span><span class="o">().</span><span class="na">isPresent</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">type</span><span class="o">.</span><span class="na">integer</span><span class="o">().</span><span class="na">get</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">type</span><span class="o">.</span><span class="na">list</span><span class="o">().</span><span class="na">isPresent</span><span class="o">())</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">type</span><span class="o">.</span><span class="na">list</span><span class="o">().</span><span class="na">get</span><span class="o">().</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="nl">TypeUtils:</span><span class="o">:</span><span class="n">display</span><span class="o">)</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">joining</span><span class="o">(</span><span class="s">", "</span><span class="o">));</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Impossible"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The implementation looks much better than the previous candidate, having only 17 lines. The wins mainly comes from
the single variant use-case, that is handled easily. To match all variants, we rely on an equivalent of instanceof,
but without sealed interfaces, we must handle the wildcard case :confused:.</p>
<p>We might be able to enhance this and remove the wildcard case, but we will have to add more methods in the <code class="language-plaintext highlighter-rouge">Type</code>
interface. Introducing “Candidate 2 (v2)”</p>
<h2 id="candidate-2-the-encapsulating-interface-v2">Candidate 2: the encapsulating interface (v2)</h2>
<p>In this implementation, we will add a new method, that is basically a functional version of the visitor pattern.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">match</span><span class="o">(</span><span class="nc">Function</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchString</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Integer</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchInteger</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchList</span><span class="o">);</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">StringType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="o">...</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">match</span><span class="o">(</span><span class="nc">Function</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchString</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Integer</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchInteger</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchList</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">matchString</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">IntType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="o">...</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">match</span><span class="o">(</span><span class="nc">Function</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchString</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Integer</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchInteger</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchList</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">matchInteger</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">ListType</span> <span class="kd">implements</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="o">...</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">match</span><span class="o">(</span><span class="nc">Function</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchString</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Integer</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchInteger</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchList</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">matchList</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This implementation adds 13 more lines, but allow us to give a pattern-matching feeling when implementing <code class="language-plaintext highlighter-rouge">display</code>.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">display</span><span class="o">(</span><span class="nc">Type</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">type</span><span class="o">.</span><span class="na">match</span><span class="o">(</span>
<span class="n">s</span> <span class="o">-></span> <span class="n">s</span><span class="o">,</span> <span class="c1">// Match string, could be replaced by Function.identity()</span>
<span class="n">i</span> <span class="o">-></span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">i</span><span class="o">),</span> <span class="c1">// Match int, could be replaced by String::valueOf</span>
<span class="n">l</span> <span class="o">-></span> <span class="n">l</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="nl">TypeUtils:</span><span class="o">:</span><span class="n">display</span><span class="o">).</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">joining</span><span class="o">(</span><span class="s">", "</span><span class="o">))</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The display function is down to 7 lines from 11. It’s not a big gain, but a lot of boilerplate
(<code class="language-plaintext highlighter-rouge">instanceof</code> or <code class="language-plaintext highlighter-rouge">isPresent</code> / <code class="language-plaintext highlighter-rouge">get</code> calls) is gone.</p>
<p>The second version of candidate 2 is obviously bigger in terms of code, and longer to write, but offers the best API
(as for now). Maintaining the <code class="language-plaintext highlighter-rouge">match</code> function can be tedious though, a type with a lot of variants will create a
function with a lot of parameters.</p>
<h2 id="candidate-3-the-single-class">Candidate 3: the single class</h2>
<p>Candidate 3’s goal is to try to reduce the size of <code class="language-plaintext highlighter-rouge">Type</code> implementation. Instead of inheritance, we can use a single
class containing all the variants. We can enforce this class to have only one variant being set using constraints
enforced by methods <sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>. This unfortunately means no enforcement by design, so the implementation needs to be reviewed
and tested correctly. Any new addition to this class must also be reviewed correctly.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Type</span> <span class="o">{</span>
<span class="nd">@Nullable</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">string</span><span class="o">;</span>
<span class="nd">@Nullable</span>
<span class="kd">private</span> <span class="nc">Integer</span> <span class="n">integer</span><span class="o">;</span>
<span class="nd">@Nullable</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">list</span><span class="o">;</span>
<span class="c1">// Private constructor</span>
<span class="c1">// Types are constructed with static factory methods below</span>
<span class="kd">private</span> <span class="nf">Type</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="nc">String</span> <span class="n">string</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">Integer</span> <span class="n">integer</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">string</span> <span class="o">=</span> <span class="n">string</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">integer</span> <span class="o">=</span> <span class="n">integer</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">list</span> <span class="o">=</span> <span class="n">list</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="nf">string</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">string</span><span class="o">);</span> <span class="o">}</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">integer</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">integer</span><span class="o">);</span> <span class="o">}</span>
<span class="kd">public</span> <span class="nc">Optional</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>></span> <span class="nf">list</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="nc">Optional</span><span class="o">.</span><span class="na">ofNullable</span><span class="o">(</span><span class="n">list</span><span class="o">);</span> <span class="o">}</span>
<span class="kd">public</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="no">T</span> <span class="nf">match</span><span class="o">(</span><span class="nc">Function</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchString</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Integer</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchInteger</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">>,</span> <span class="no">T</span><span class="o">></span> <span class="n">matchList</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">string</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">matchString</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">string</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">integer</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">matchInteger</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">integer</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">list</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">matchList</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">list</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Invalid"</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// These factory methods ensure that only one variant is set</span>
<span class="kd">static</span> <span class="nc">Type</span> <span class="nf">string</span><span class="o">(</span><span class="nc">String</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">Type</span><span class="o">(</span><span class="n">value</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">static</span> <span class="nc">Type</span> <span class="nf">integer</span><span class="o">(</span><span class="kt">int</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">Type</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="n">value</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">static</span> <span class="nc">Type</span> <span class="nf">list</span><span class="o">(</span><span class="nc">List</span><span class="o"><</span><span class="nc">Type</span><span class="o">></span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">Type</span><span class="o">(</span><span class="kc">null</span><span class="o">,</span> <span class="kc">null</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This implementation takes 45 lines, while offering the same API as the previous candidate. It has also the advantage
of only requiring a single implementation of <code class="language-plaintext highlighter-rouge">equals</code> & <code class="language-plaintext highlighter-rouge">hashCode</code>. It also looks more like a value class <sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>.</p>
<p>However, this object does not have the same layout in terms of memory as the previous candidates. Previous
implementations of <code class="language-plaintext highlighter-rouge">Type</code> only hold metadata and the fields of the specific variants, this <code class="language-plaintext highlighter-rouge">Type</code> class will always
have 3 members, one per variant. This can cause an impact on memory consumption, especially when variants are added
to the type.</p>
<p>This implementation also contains a wildcard throw in it’s <code class="language-plaintext highlighter-rouge">match</code> implementation, as it is impossible for the Java
compiler to understand that the 3 ifs covers all the state that can be reached in this class.</p>
<h2 id="candidate-4-the-matcher">Candidate 4: the matcher</h2>
<p>This last candidate is a departure from the previous patterns. In the previous candidates, we wanted to offer a
safe API to the developer, especially about being able to enforce the different variants that are allowed in
the sum-type.</p>
<p>This candidate relaxes this constraint, and will simply operate on <code class="language-plaintext highlighter-rouge">Object</code>s. Our goal will simply to offer a
pattern-matching like API for a subset of types. This can be useful on existing classes, without needing a heavy
refactoring.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Matcher</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Object</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">defaultMatcher</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">List</span><span class="o"><</span><span class="nc">MatcherItem</span><span class="o"><</span><span class="no">T</span><span class="o">>></span> <span class="n">items</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="kd">public</span> <span class="nf">Matcher</span><span class="o">(</span><span class="nc">Function</span><span class="o"><</span><span class="nc">Object</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">defaultMatcher</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">defaultMatcher</span> <span class="o">=</span> <span class="n">defaultMatcher</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="nc">Matcher</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="nf">withDefault</span><span class="o">(</span><span class="nc">Function</span><span class="o"><</span><span class="nc">Object</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">defaultMatcher</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">Matcher</span><span class="o"><>(</span><span class="n">defaultMatcher</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="o"><</span><span class="no">U</span><span class="o">></span> <span class="nc">Matcher</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="nf">with</span><span class="o">(</span><span class="nc">Class</span><span class="o"><</span><span class="no">U</span><span class="o">></span> <span class="n">clazz</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="no">U</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matcher</span><span class="o">)</span> <span class="o">{</span>
<span class="n">items</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">MatcherItem</span><span class="o"><>(</span><span class="n">clazz</span><span class="o">,</span> <span class="n">o</span> <span class="o">-></span> <span class="n">matcher</span><span class="o">.</span><span class="na">apply</span><span class="o">((</span><span class="no">U</span><span class="o">)</span><span class="n">o</span><span class="o">)));</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="no">T</span> <span class="nf">match</span><span class="o">(</span><span class="nc">Object</span> <span class="n">object</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Through metaprogramming, we find if the input object</span>
<span class="c1">// is compatible with the input class type. If it is the case, we call</span>
<span class="c1">// the passed match function. Otherwise, we call the default match function.</span>
<span class="k">return</span> <span class="n">items</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">item</span> <span class="o">-></span> <span class="n">item</span><span class="o">.</span><span class="na">isCompatible</span><span class="o">(</span><span class="n">object</span><span class="o">))</span>
<span class="o">.</span><span class="na">findFirst</span><span class="o">()</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">item</span> <span class="o">-></span> <span class="n">item</span><span class="o">.</span><span class="na">matcher</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">object</span><span class="o">))</span>
<span class="o">.</span><span class="na">orElseGet</span><span class="o">(()</span> <span class="o">-></span> <span class="n">defaultMatcher</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">object</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MatcherItem</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Class</span><span class="o"><?></span> <span class="n">clazz</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Object</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matcher</span><span class="o">;</span>
<span class="kd">private</span> <span class="nf">MatcherItem</span><span class="o">(</span><span class="nc">Class</span><span class="o"><?></span> <span class="n">clazz</span><span class="o">,</span> <span class="nc">Function</span><span class="o"><</span><span class="nc">Object</span><span class="o">,</span> <span class="no">T</span><span class="o">></span> <span class="n">matcher</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">clazz</span> <span class="o">=</span> <span class="n">clazz</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">matcher</span> <span class="o">=</span> <span class="n">matcher</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">isCompatible</span><span class="o">(</span><span class="nc">Object</span> <span class="n">object</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">object</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">isAssignableFrom</span><span class="o">(</span><span class="n">clazz</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This matcher implementation might look confusing, but is very convenient in practice:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Generics is very annoying in Java, especially when you want to use meta-programming.</span>
<span class="c1">// Here we wrap the generic class into something more concrete. this is a workaround</span>
<span class="kd">class</span> <span class="nc">ObjectList</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Object</span><span class="o">></span> <span class="n">objects</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">TypeUtils</span> <span class="o">{</span>
<span class="nd">@Nullable</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">getString</span><span class="o">(</span><span class="nc">Object</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// (bad) type inference force to do this. As we are unable to pass the return type</span>
<span class="c1">// we need to explicitly create a parameter of type Matcher and annotate the return type</span>
<span class="nc">Matcher</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">matcher</span> <span class="o">=</span> <span class="nc">Matcher</span><span class="o">.</span><span class="na">withDefault</span><span class="o">(</span><span class="n">o</span> <span class="o">-></span> <span class="kc">null</span><span class="o">);</span> <span class="c1">// Here we retur null, Java cannot infer what's the return type</span>
<span class="k">return</span> <span class="n">matcher</span><span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">s</span> <span class="o">-></span> <span class="n">s</span><span class="o">)</span>
<span class="o">.</span><span class="na">match</span><span class="o">(</span><span class="n">type</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">display</span><span class="o">(</span><span class="nc">Object</span> <span class="n">type</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Matcher</span><span class="o">.</span><span class="na">withDefault</span><span class="o">(</span><span class="n">o</span> <span class="o">-></span> <span class="s">""</span><span class="o">)</span> <span class="c1">// If the function returns the type already, everything compiles :)</span>
<span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">s</span> <span class="o">-></span> <span class="n">s</span><span class="o">)</span>
<span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="nc">Integer</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">i</span> <span class="o">-></span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">i</span><span class="o">))</span>
<span class="o">.</span><span class="na">with</span><span class="o">(</span><span class="nc">ObjectList</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">l</span> <span class="o">-></span> <span class="n">l</span><span class="o">.</span><span class="na">objects</span><span class="o">.</span><span class="na">stream</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="n">o</span> <span class="o">-></span> <span class="n">display</span><span class="o">(</span><span class="n">o</span><span class="o">)).</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">joining</span><span class="o">(</span><span class="s">", "</span><span class="o">)))</span>
<span class="o">.</span><span class="na">match</span><span class="o">(</span><span class="n">type</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This candidate can be annoying or delightful to use, depending on your opinion :slightly_smiling_face:. At least it is
vaguely similar to the class based pattern matching that will be introduced in the future.</p>
<h2 id="verdict">Verdict</h2>
<p>Let’s try to do a more-or-less objective comparison of all these candidates</p>
<table>
<thead>
<tr>
<th> </th>
<th>Implementation</th>
<th>Usage</th>
<th>Comments</th>
</tr>
</thead>
<tbody>
<tr>
<td>Visitor</td>
<td>1/5</td>
<td>3/5</td>
<td>Known pattern, canonical implementation. Very verbose, in implementation and usage.</td>
</tr>
<tr>
<td>Encapsulating interface</td>
<td>3/5</td>
<td>4/5</td>
<td>Quite good API. Still similar to the visitor pattern. Still a bit verbose in implementation.</td>
</tr>
<tr>
<td>Single class</td>
<td>4/5</td>
<td>4/5</td>
<td>Quite good API. Looks like a value class. Still a bit verbose in implementation. Implementation needs to be reviewed. Be careful of memory cost.</td>
</tr>
<tr>
<td>Matcher</td>
<td>2/5</td>
<td>2/5</td>
<td>Can be integrated into any codeline. Implementation is complex. Uses metaprogramming. Not really a sum-type.</td>
</tr>
</tbody>
</table>
<p>My personal preference goes to the single class design, simply because it resembles a value class. The encapsulating
interface and visitor can also be good designs, as they are quite close to the future way of defining sum types in Java.
The matcher design is simply here just in case, but should be considered only as a last resort.</p>
<hr />
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>And many companies. Java is not hype, but is well-known production grade language. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>I tend to classify classes in two categories: values and services. Value classes carries values (hence the name),
while service classes operate on values to produce new values. Example of value classes are dates, x-y coordinates, an
HTML tag, etc. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a> <a href="#fnref:2:1" class="reversefootnote" role="doc-backlink">↩<sup>2</sup></a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>Preview in Java 17 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:4" role="doc-endnote">
<p>This is the <em>real</em> purpose of encapsulation. Encapsulation is about enforcing constraints, not about hiding
members and provide getters. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:5" role="doc-endnote">
<p>Intuitively, value classes have “natural” constructors, getters, equals and hashCodes. These natural operators
are implemented by delegating to operators on each member. In the case of an inheritance hierarchy, members might not
be the same, so it’s impossible to implement these natural operators. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>The goal of this post is to find the perfect sum type implementation in Java.My first Rust crate, pretend2021-05-10T00:00:00+00:002021-05-10T00:00:00+00:00http://sfietkonstantin.github.io/2021/05/10/First-Crate-Pretend<p>I’m very happy to publish my first crate on crates.io, <a href="https://crates.io/crates/pretend"><code class="language-plaintext highlighter-rouge">pretend</code></a> :tada:</p>
<p>As a play on word on <a href="https://github.com/OpenFeign/feign">Feign</a>, my source of inspiration, it is a declarative HTTP
and REST client.</p>
<h2 id="my-love-for-feign-">My love for Feign …</h2>
<p>I have written a few Java/Spring based micro-services before discovering feign. To communicate between micro-services,
I used to write my HTTP requests by hand, using the Apache HTTP client. It was a bit tedious, as I had to write a lot
of boilerplate code to build the URL, set the headers, serialize and deserialize bodies etc.</p>
<p>Moreover, the code didn’t reflect the REST API I call. Since the code to create and execute a request could be very
big, it’s harder to find out at a quick glance which endpoint is being called by just looking at the code.</p>
<p>Then I discovered Feign, and my life changed a bit. Thanks to the annotations, it was easy to describe the REST API
I want to use. All the boilerplate is also gone, as Feign was in charge of creating and executing the request.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">interface</span> <span class="nc">GitHub</span> <span class="o">{</span>
<span class="nd">@RequestLine</span><span class="o">(</span><span class="s">"GET /repos/{owner}/{repo}/contributors"</span><span class="o">)</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Contributor</span><span class="o">></span> <span class="nf">contributors</span><span class="o">(</span><span class="nd">@Param</span><span class="o">(</span><span class="s">"owner"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">owner</span><span class="o">,</span> <span class="nd">@Param</span><span class="o">(</span><span class="s">"repo"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">repo</span><span class="o">);</span>
<span class="nd">@RequestLine</span><span class="o">(</span><span class="s">"POST /repos/{owner}/{repo}/issues"</span><span class="o">)</span>
<span class="kt">void</span> <span class="nf">createIssue</span><span class="o">(</span><span class="nc">Issue</span> <span class="n">issue</span><span class="o">,</span> <span class="nd">@Param</span><span class="o">(</span><span class="s">"owner"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">owner</span><span class="o">,</span> <span class="nd">@Param</span><span class="o">(</span><span class="s">"repo"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">repo</span><span class="o">);</span>
<span class="o">}</span>
<span class="nc">GitHub</span> <span class="n">github</span> <span class="o">=</span> <span class="nc">Feign</span><span class="o">.</span><span class="na">builder</span><span class="o">().</span><span class="na">target</span><span class="o">(</span><span class="nc">GitHub</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="s">"https://api.github.com"</span><span class="o">);</span>
</code></pre></div></div>
<p>Feign is also very modular, and allows changing nearly every component it uses, from the HTTP client to “retryers”
or the serialization framework.</p>
<h2 id="-and-my-love-for-rust">… and my love for Rust</h2>
<p>While I’m mainly an application developer, and “simply” want to put Rust in some odd places <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, I really wanted to
contribute to its ecosystem.</p>
<p>Today, I’m facing the same problems I faced when using the Apache HTTP client: boilerplate. While Rust HTTP clients
are really well-designed and often offer a lot of possibilities, I am in search of expressiveness. Fortunately, Rust’s
procedural macros is really powerful, and allows replicating what makes Feign great, the annotation system.</p>
<p>After tinkering with <code class="language-plaintext highlighter-rouge">syn</code>, <code class="language-plaintext highlighter-rouge">quote</code> and friends, as well as digging into various HTTP client documentations, I’m
now happy to present <code class="language-plaintext highlighter-rouge">pretend</code></p>
<h2 id="introducing-pretend">Introducing pretend</h2>
<p><code class="language-plaintext highlighter-rouge">pretend</code> is an async-first HTTP client. By annotating traits with some attributes, it will automatically implement
these traits so that they can be used.</p>
<p><code class="language-plaintext highlighter-rouge">pretend</code> is not really an HTTP client as it doesn’t perform any request. Just like Feign, it tries to be modular
and uses client implementations. The first release ships integration with
<a href="https://docs.rs/reqwest/latest/reqwest/"><code class="language-plaintext highlighter-rouge">reqwest</code></a> and <a href="https://docs.rs/isahc/latest/isahc/"><code class="language-plaintext highlighter-rouge">isahc</code></a>.</p>
<p>Here is an example of how to use <code class="language-plaintext highlighter-rouge">pretend</code>. You can learn more by looking at the
<a href="https://docs.rs/pretend/0.1.0/pretend/">documentation</a> (when it is ready …).</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">pretend</span><span class="p">::{</span><span class="n">pretend</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">Pretend</span><span class="p">,</span> <span class="n">Result</span><span class="p">,</span> <span class="n">Url</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">pretend_reqwest</span><span class="p">::</span><span class="n">Client</span><span class="p">;</span>
<span class="nd">#[pretend]</span>
<span class="k">trait</span> <span class="n">HttpBin</span> <span class="p">{</span>
<span class="nd">#[request(method</span> <span class="nd">=</span> <span class="s">"POST"</span><span class="nd">,</span> <span class="nd">path</span> <span class="nd">=</span> <span class="s">"/anything"</span><span class="nd">)]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">post_anything</span><span class="p">(</span><span class="o">&</span><span class="k">self</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span> <span class="o">&</span><span class="nv">'static</span> <span class="nb">str</span><span class="p">)</span> <span class="k">-></span> <span class="n">Result</span><span class="o"><</span><span class="nb">String</span><span class="o">></span><span class="p">;</span>
<span class="p">}</span>
<span class="nd">#[tokio::main]</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">client</span> <span class="o">=</span> <span class="nn">Client</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span>
<span class="k">let</span> <span class="n">url</span> <span class="o">=</span> <span class="nn">Url</span><span class="p">::</span><span class="nf">parse</span><span class="p">(</span><span class="s">"https://httpbin.org"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">let</span> <span class="n">pretend</span> <span class="o">=</span> <span class="nn">Pretend</span><span class="p">::</span><span class="nf">for_client</span><span class="p">(</span><span class="n">client</span><span class="p">)</span><span class="nf">.with_url</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
<span class="c">// pretend annotated traits are automatically implemented for Pretend</span>
<span class="k">let</span> <span class="n">response</span> <span class="o">=</span> <span class="n">pretend</span><span class="nf">.post_anything</span><span class="p">(</span><span class="s">"hello"</span><span class="p">)</span><span class="k">.await</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">assert</span><span class="o">!</span><span class="p">(</span><span class="n">response</span><span class="nf">.contains</span><span class="p">(</span><span class="s">"hello"</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>By reusing clients, I hope to not land in this situation :slightly_smiling_face:</p>
<p><img src="https://imgs.xkcd.com/comics/standards.png" alt="XKCD: how standards proliferate" /></p>
<h2 id="feedbacks-welcomed-">Feedbacks welcomed !</h2>
<p>Feedbacks are warmly welcomed. If this crate is missing some features don’t hesitate to contact me on
<a href="https://twitter.com/SfietKonstantin">Twitter</a> or to leave an issue on
<a href="https://github.com/SfietKonstantin/pretend">Github</a>.</p>
<hr />
<h2 id="ps-other-similar-crates">PS: Other similar crates</h2>
<p><a href="https://crates.io/crates/feignhttp"><code class="language-plaintext highlighter-rouge">feignhttp</code></a> fills a similar role. It doesn’t use traits but automatically
implement functions instead. It seems to even shares the same inspiration.</p>
<p>There is also <a href="https://crates.io/crates/mclient"><code class="language-plaintext highlighter-rouge">mclient</code></a> that also implement functions.</p>
<hr />
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>Like in a phone running GNU/Linux <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>I’m very happy to publish my first crate on crates.io, pretend :tada:Sailfish OS and Fedora 332021-04-29T00:00:00+00:002021-04-29T00:00:00+00:00http://sfietkonstantin.github.io/2021/04/29/Sailfish-SDK-Fedora<p>TL;DR: Do not use the Docker-based Sailfish SDK on an out-of-the-box installation of Fedora. Either use the VirtualBox
based setup or use Docker after enabling cgroups v1 compatibility.</p>
<p>If you really want to try to use Podman, you can try the “wrapper script” from
<a href="https://github.com/SfietKonstantin/sailfishos-sdk-rust-hacks/tree/main/sdk-podocker">this repository</a>.</p>
<hr />
<p>This blog post covers my many attempts to use the Sailfish SDK on Fedora 33 to build a Rust-based application.</p>
<p>Spoiler alert: it didn’t went well :confused:.</p>
<h2 id="the-fedora-experience">The Fedora experience</h2>
<p>Long time ago, after distro-hopping from Mandrivas, Ubuntus and OpenSUSE, I have settled on using Fedora for my daily
driver. It is a nice distro, being both reliable and innovative. I’m trying to not tweak it’s out-of-the-box
configuration, to “benefit” from all the features that are shipped with it.</p>
<p>While most of these features were welcoming or at least invisible, some of them are more controversial for end-users.
For example, I still don’t see the use of SELinux, that often get into my way.</p>
<p>But today, we are going to talk about another of these kind of features, introduced by Fedora 31: cgroups v2. While I
don’t know what it brings (and didn’t read about it), this change made Docker unavailable for Fedora <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.
Fortunately, <a href="https://podman.io/">podman</a> is proposed as a drop-in replacement. The project website even recommend
aliasing <code class="language-plaintext highlighter-rouge">docker</code> to <code class="language-plaintext highlighter-rouge">podman</code> !</p>
<p>After using Podman for some time, I was ready to state that it was indeed a good replacement for Docker, until that day
where I wanted to (re)use the Sailfish SDK to build some Rust code.</p>
<h2 id="the-not-so-drop-in-replacement">The not-so drop-in replacement</h2>
<p>The Sailfish SDK packages a cross-compiler, Scratchbox 2, inside an OS image. The SDK boots this image to start the
“build engine”. It can either use Virtual Box or Docker. When the build engine is started, the SDK communicates with it
with SSH.</p>
<p>Armed with my trust in Podman, I created a symbolic link from <code class="language-plaintext highlighter-rouge">podman</code> to <code class="language-plaintext highlighter-rouge">docker</code> and installed the SDK by choosing
the Docker option. The installation went well but unfortunately, inside the SDK, the build engine could not be started.</p>
<figure class="image">
<img src="/images/2021-04-29/sailfish-sdk-no-vm.png" alt="An error message about virtual machine sailfish-os-build-engine that cannot be found" />
<figcaption>I'm pretty sure my installation went well ...</figcaption>
</figure>
<p>After looking at the logs <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> and into the code <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>, I found out that the SDK is trying to find an image named
<code class="language-plaintext highlighter-rouge">sailfish-os-build-engine</code> and, when found, starting a container with the same name. This works fine with Docker, but
Podman prefixes all local images with <code class="language-plaintext highlighter-rouge">localhost</code>. On my machine, the image is then named
<code class="language-plaintext highlighter-rouge">localhost/sailfish-os-build-engine</code>.</p>
<p>I tried to cheat a bit and configure a build engine named <code class="language-plaintext highlighter-rouge">localhost/sailfish-os-build-engine</code>, but this didn’t worked
either. The SDK will always use the image name for the container name and slashes are forbidden in container
names.</p>
<p>After countless hours searching for various solution on the Internet, in the hope of finding the magic switch that
disable this <code class="language-plaintext highlighter-rouge">localhost</code> prefix, I ended by concluding that this is impossible <sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>. Podman isn’t a drop-in replacement
for Docker after all.</p>
<p>I still had a way to make the SDK work. By introducing a little change in the SDK, I could remove the prefix when
listing images. This should make the SDK compatible with Podman. After a rebuild of Qt Creator and a tentative
pull-request, my patched SDK was ready.</p>
<h2 id="a-bus-problem">A bus problem</h2>
<p>The rebuilt SDK allowed me to start the engine, but didn’t seems to acknowledge that it started. Trying to SSH also
lead to an error.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ssh <span class="nt">-p</span> 2222 <span class="nt">-i</span> ~/Dev/SailfishOS/SDK/vmshare/ssh/private_keys/engine/mersdk mersdk@localhost
kex_exchange_identification: <span class="nb">read</span>: Connection reset by peer
Connection reset by ::1 port 2222
</code></pre></div></div>
<p>The engine seems to reject SSH connections, so as a last resort, I executed a shell directly in the container …</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>podman <span class="nb">exec</span> <span class="nt">-it</span> sailfish-os-build-engine /bin/bash
Failed to connect to bus: No such file or directory
</code></pre></div></div>
<p>… and discovered another class of issues.</p>
<p>Remember that the Sailfish OS SDK build engine run either as a VM or a container. When running in a VM, a fully
fledged Linux distribution is booted, including a SSH server and a web-service.</p>
<p>The Docker based build engine should behave similarly as the VM. In fact, the entrypoint of the container is
<code class="language-plaintext highlighter-rouge">/sbin/init</code> ie <code class="language-plaintext highlighter-rouge">systemd</code>, so when the container is started, <code class="language-plaintext highlighter-rouge">systemd</code> will be in charge of initializing various daemons
in the container, including the SSH server.</p>
<p>The bus error I’m receiving seems to indicate that the container could not interact with systemd running on host. I’m
suspecting that the change to cgroups v2 is still causing trouble here. Enabling cgroups v1 compatibility seems to fix
this issue as reported by
<a href="https://forum.sailfishos.org/t/issues-with-installing-sailfish-sdk-docker-with-debian-bullseye-11">this thread</a>.</p>
<h2 id="introducing-second-order-hacks">Introducing second order hacks</h2>
<p>In the meantime, SDK developers suggested to use an alternative docker executable. In fact, the docker command to
be used can be configured via an ini file.</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># <sdk-root>/share/qtcreator/SailfishSDK/libsfdk.ini
</span>
<span class="c"># Set the path to your docker executable
</span><span class="py">DockerPath</span><span class="p">=</span><span class="s">/home/sk/Dev/SailfishOS/sdk-podocker/target/debug/sdk-podocker</span>
</code></pre></div></div>
<p>So instead of changing how the SDK lists images or starts container, I could simply write a script that wraps Docker or
Podman. This script would be in charge of</p>
<ul>
<li>Patching the output of <code class="language-plaintext highlighter-rouge">podman images</code></li>
<li>Start the SSH server inside the build engine container after <code class="language-plaintext highlighter-rouge">podman start</code></li>
</ul>
<p>I have written a very small Rust program, located in
<a href="https://github.com/SfietKonstantin/sailfishos-sdk-rust-hacks/tree/main/sdk-podocker">this repository</a>. You can try it
but, as you will see later, I do not recommend using it as it didn’t resolve all my issues.</p>
<h2 id="will-it-build-">Will it build ?</h2>
<p>When using the wrapper script, the SDK finally recognizes the build engine and could start to build. However, I faced
a lot of permissions errors when writing to the host’s file system from inside the container.</p>
<p>Podman in a rootless mode performs id mapping: files on the host file system and mapped using volumes will appear as
owned by <code class="language-plaintext highlighter-rouge">root</code> inside the container. And when root writes files on the mapped volumes, they will be owned by the host’s
(non-root) user. The build engine do not use root for most of it’s operations. Instead it is using the <code class="language-plaintext highlighter-rouge">mersdk</code> user,
that do not have permission to write in <code class="language-plaintext highlighter-rouge">root</code>-owned folders.</p>
<p>While this permissions issue is still something manageable <sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>, I feel that the giant pile of hacks I’m starting to
build didn’t feel sustainable. But the final nail in the <del>coffin</del> container came with Rust: when following this
<a href="https://forum.sailfishos.org/t/rust-howto-request/3187">forum.sailfishos.org</a> thread to install Rust,
I managed to break my build engine <sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">6</a></sup>.</p>
<h2 id="conclusion">Conclusion</h2>
<div>
<blockquote class="imgur-embed-pub" lang="en" data-id="rQIb4Vw">
<a href="//imgur.com/rQIb4Vw">Replacing a lightbulb</a>
</blockquote>
<script async="" src="//s.imgur.com/min/embed.js" charset="utf-8">
</script>
</div>
<p>At start, I simply wanted to build some Rust code. But I got carried away quite far. In the meantime, we have seen
the differences between Docker and Podman, Podman specificities and how the Sailfish SDK works.</p>
<p>For Fedora users, I recommend to stick with Virtual Box based SDK. You can also try to using Docker and reenable
cgroups v1 by passing<code class="language-plaintext highlighter-rouge">systemd.unified_cgroup_hierarchy=false</code> to the kernel command line. I do not recommend using
Podman for the SDK <sup id="fnref:7" role="doc-noteref"><a href="#fn:7" class="footnote" rel="footnote">7</a></sup>.</p>
<hr />
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>I have learned today that Docker is now compatible with cgroups v2. I might revisit one day my decision to live
without Docker. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>I take this opportunity to give Kudos to Jolla developers for caring and improving their SDK. I was amazed by
the amount of changes since the last time I used it ! Some highlights: Docker support and the CLI. Running the
CLI with debug logs helped me a lot. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p><a href="https://github.com/sailfishos/sailfish-qtcreator">https://github.com/sailfishos/sailfish-qtcreator</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:4" role="doc-endnote">
<p>Comment in <a href="https://github.com/containers/buildah/issues/1034">this pull-request</a> suggest that the prefix is
intended and cannot be disabled. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:5" role="doc-endnote">
<p><code class="language-plaintext highlighter-rouge">chmod 777</code> is still an option. <a href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:6" role="doc-endnote">
<p>More on this on a follow-up article. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:7" role="doc-endnote">
<p>I do recommend using Podman for containers though. The daemon-less, root-less design is really nice, but simply
doesn’t work with the Sailfish SDK. <a href="#fnref:7" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>TL;DR: Do not use the Docker-based Sailfish SDK on an out-of-the-box installation of Fedora. Either use the VirtualBox based setup or use Docker after enabling cgroups v1 compatibility.