<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Hasufell's blog</title>
    <link href="https://github.com/hasufell/hasufell.github.io/atom.xml" rel="self" />
    <link href="https://github.com/hasufell/hasufell.github.io" />
    <id>https://github.com/hasufell/hasufell.github.io/atom.xml</id>
    <author>
        <name>Julian Ospald</name>
        <email>hasufell@posteo.de</email>
    </author>
    <updated>2026-02-28T00:00:00Z</updated>
    <entry>
    <title>Yet another opinion on LLMs</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2026-02-28-LLMs.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2026-02-28-LLMs.html</id>
    <published>2026-02-28T00:00:00Z</published>
    <updated>2026-02-28T00:00:00Z</updated>
    <summary type="html"><![CDATA[<h2 id="toc">TOC</h2>
<ul>
<li><a href="#opinion">Opinion</a></li>
<li><a href="#what-is-good-use-of-llms">What is good use of LLMs</a></li>
<li><a href="#using-llms-to-build-things">Using LLMs to build things</a></li>
<li><a href="#what-now">What now?</a></li>
</ul>
<h2 id="opinion">Opinion</h2>
<p>After I’ve been exposed to LLMs more frequently at work doing Haskell, there are a couple of things that have crystallized for me:</p>
<ol type="1">
<li>they are exceptionally poor at reasoning</li>
<li>they lie more often than not</li>
<li>they are gigantic guessing/templating machines</li>
</ol>
<h2 id="what-is-good-use-of-llms">What is good use of LLMs</h2>
<p>The issue with using LLMs isn’t just the well-known hallucination issue, which is btw. massively understated, it’s also that it will not by default go out of its way to find anything but the most average answer.</p>
<p>If you’re dealing with complicated problems you’ll end up with messy prompts and lots of back and forth, because the agent will just shuffle through possible answers, starting with the most direct one. You’ll keep pointing out flaws or saying it’s plain wrong and it will just try the next possibility.</p>
<p>After all, it doesn’t really reason, it’s just guessing. But once we know that, that’s fine.</p>
<p>The consequences of that is what I feel many people easily overlook:</p>
<ul>
<li>the only reasonable way to use LLMs is when you can trivially verify the output, or…</li>
<li>when you have superior domain knowledge and it becomes a sparring partner</li>
</ul>
<p>This can be tricky. When dealing with e.g. bugs, there’s often a way to simply try the suggested solution and see if it solves the issue. When you don’t have a way to easily verify the output via trial and error, then you’ll have to have extensive knowledge in the domain, so you can guide the LLM and tell it where it’s wrong. But once you don’t know the domain very well, you’re screwed. And that’s unfortunately one of the main use cases: I found myself asking Claude a lot of questions about linking and FreeBSD issues. I got lied to and went off-course so many times, that I’m confident to say it made me less productive. But when I used it in domains I have extensive knowledge myself, I could spot the lies and expose them. Then after a while it would get things right. Whether that actually saved me any time, I’m also not sure, but I’m sure there are cases where it did. But even then I rarely enjoyed the interactions.</p>
<p>Another implication is that using LLMs for summaries or to broadly explaining a codebase is improper use as well, because you have no idea how accurate it is. On the other hand, using it for <em>navigating</em> a codebase is much less risky, because if it sends you in a wrong direction, the worst thing that can happen is that you read more code.</p>
<h2 id="using-llms-to-build-things">Using LLMs to build things</h2>
<p>I’m not into vibe coding at all. My opinion is that it isn’t much different from copy-pasting code from StackOverflow. Back in the day, we used to call those people poor engineers. Today with the use of AI it’s suddenly fancy.</p>
<p>There are many reasons why I think it’s inappropriate use:</p>
<ul>
<li>it’s not good at novel problems (as in: things outside of the training set)</li>
<li>it still produces average code</li>
<li>it doesn’t really design anything organically</li>
</ul>
<p>I think the best comparsion is the blog post <a href="https://blog.yossarian.net/2025/01/10/Be-aware-of-the-Makefile-effect">Be aware of the Makefile effect</a>, which argues that most people copy paste a known good solution into a new context and then make small adjustments. I think vibe coding takes this to a whole new level. You start with a piece of code that Claude throws out and then adjust it here and there until it works.</p>
<p>This is not how a good programmer discovering a new problem works though: you think about the issue holistically, develop a mental model and then decide how you express it in code, weighing competing concerns like extensibility, performance, simplicity and so on.</p>
<p>When vibe coding, the output of code is too much and too fast for these fundamental mental processes to have enough time to take place. You’re busy navigating the pieces and composing them and interacting with the prompt. But you’re not really designing anything. You’re guiding a copy-paste machine. I think it’s highly unlikely that the code would look the same way if you had written it from scratch. And I think that’s a useful metric to have.</p>
<p>But you could say it excels in prototyping and maybe that’s true… but prototypes have the tendency to become the first released version. You’d have to actually delete the code and start from scratch and only use the <em>experience</em> you gained from the prototype. But did you actually gain experience there?</p>
<h2 id="the-social-impact">The social impact</h2>
<p>I don’t want to talk about the energy consumption problem, the AI funding bubble, etc., but about the social impact on us programmers.</p>
<p>I’ve had cases, where:</p>
<ul>
<li>people commit AI generated files/patches and ask for a review</li>
<li>I ask someone to explain something to me and they run my question through an LLM and paste the output to me</li>
<li>during an online argument, someone suddenly pastes AI-slop into the chat, because maybe they got bored of the conversation</li>
</ul>
<p>I find all of these cases incredibly frustrating. If you want me to review the output of your Claude conversation, you’re essentially asking me to do your job. It’s not a review. If I ask for your help, I’m asking you. I can run my questions through an AI prompt myself, I don’t need you for that. It’s almost a case of “Let Me Google That For You” and I find it disrespectful.</p>
<p>Apart from that, there’s already growing concern that <a href="https://www.theguardian.com/technology/2025/oct/15/pupils-fear-ai-eroding-study-ability-research">AI is actually bad for learning</a> and the video <a href="https://youtu.be/0xS68sl2D70?si=FJrp3O1fGTWJaGv2">Veritasium: What Everyone Gets Wrong About AI</a> explores that question as well, but also something more fundamental: <strong>the difference between two systems of thought</strong>:</p>
<ul>
<li>system one: fast, partly subconscious thinking (also utilizing experience/memory to come to an answer more quickly)</li>
<li>system two: slow, conscious, effortful, methodical thinking</li>
</ul>
<p>Neither of them is good or bad, but we have a tendency to over-utilize system one, because it’s good when dealing with high throughput and lots of stimuli… filtering through data quickly. But sometimes it leads us off-course, especially when we underestimate the problem.</p>
<p>When we’re using LLM to support us in the quest to maximize our productivity, I’d argue we’re losing our ability to make use of system two even more, because instead of taking a step back and thinking deeply about the problem, I can just have LLM shuffle through possible answers, try them out and move on. But I’ll also not remember the interaction, nor the answer! There were no “aha” moments.</p>
<p>Despite the impact on the individual or on teams, there’s also more concerning ecosystem effects. A study is arguing that <a href="https://arxiv.org/pdf/2601.15494">Vibe Coding Kills Open Source</a> and it reduces welfare despite higher productivity.</p>
<p>I personally think it also makes people engage less directly with maintainers and projects, because instead of filing bugs, they can just ask AI to find an answer or a workaround.</p>
<p>I have many more concerns: will people even bother writing documentation, will they even read this blog post or just skim through an AI summary? How will all of this shape the way we collaborate? Right now, I am mostly pessimistic.</p>
<h2 id="llm-use-outside-of-tech">LLM use outside of tech</h2>
<figure>
<img src="../images/photo_2026-02-28_14-51-12.jpg" style="width:50.0%" alt="AI reddit" /><figcaption aria-hidden="true">AI reddit</figcaption>
</figure>
<p>I’ve had more success using LLMs outside of tech as a websearch on steroids. Instead of going through 30 different reddit threads, I can get an approximate answer in seconds and also have it list all the sources. I use that when researching on things related to my road bike, new wheel sets, etc. In the end, all the information is verifiable and I use it more as a gateway to more information (as in: I actually go to the reddit threads and read them). Similar when I search about products or trying to find local shops that offer a specific inventory.</p>
<p>On the other hand, last time I searched for information regarding banking fees on international transfers, I got lied to by Gemini 4 times in a row after I verified all the information manually.</p>
<h2 id="what-now">What now?</h2>
<p>I like programming. And I like collaboration, pair programming, design discussions and so on.</p>
<p>I don’t think that AI use is making any of that more fun. To me it’s mostly a productivity gold rush. There’s some nice use cases, but most of what has been promised, didn’t actually happen. The caveats are huge and there’s many negative side effects of people relying more on these tools.</p>
<p>Although I don’t really want to go deep into the policial side of the topic, some notable Haskellers like Audrey Tang have expressed that <a href="https://english.elpais.com/technology/2025-11-02/audrey-tang-hacker-and-taiwanese-digital-minister-ai-is-a-parasite-that-fosters-polarization.html">“AI is a parasite that fosters polarization”</a>.</p>
<p>Managers seem to push for AI use, because that’s what is expected of everyone now. You’re not on top of technology or productivity if you’re not neck deep in AI subscriptions.</p>
<p>I find it a bit comical at times, because if you had a work colleague who lies 20% of the time with confidence, you’d fire them. But with the new LLM technology, we seem fine with that. Maybe because it’s much cheaper than an employee and maybe because many people don’t even notice that they’re consuming false information, because they don’t adhere to what I call “good use of LLM”.</p>
<p>There’s also some evidence that it may not actually boost productivity:</p>
<ul>
<li><a href="https://www.reuters.com/business/ai-slows-down-some-experienced-software-developers-study-finds-2025-07-10/">AI slows down some experienced software developers, study finds</a></li>
<li><a href="https://www.aol.com/articles/thousands-ceos-just-admitted-ai-183249072.html">Thousands of CEOs just admitted AI had no impact on employment or productivity</a></li>
</ul>
<p>I’ve also noticed that when you bring up these opinions that AI advocates often blame the user and say “you just don’t know how to use AI correctly”. It starts to feel a bit like a religious war at times.</p>
<p>It certainly is not just another technology. It’s quite different from previous technological advancements.</p>]]></summary>
</entry>
<entry>
    <title>Dynamic GHC matrix in GitHub CI</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2026-01-22-dynamic-ghcup-action.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2026-01-22-dynamic-ghcup-action.html</id>
    <published>2026-01-22T00:00:00Z</published>
    <updated>2026-01-22T00:00:00Z</updated>
    <summary type="html"><![CDATA[<h2 id="toc">TOC</h2>
<ul>
<li><a href="#how-to">How to</a></li>
<li><a href="#reacting-to-failures">Reacting to failures</a></li>
<li><a href="#all-put-together">All put together</a></li>
<li><a href="#a-few-notes">A few notes</a></li>
<li><a href="#prior-art">Prior Art</a></li>
</ul>
<h2 id="how-to">How to</h2>
<p>When new GHC versions are released, I often have to go through all my library CI systems and update the ghc version matrix in my github action. You could argue a CI generation system like <a href="https://github.com/haskell-CI/haskell-ci">haskell-CI</a> makes this somewhat simpler, but I’m a strong opponent of generating CI configurations, because:</p>
<ul>
<li>it makes debugging harder (where does the line in my 800 LOC config come from?)</li>
<li>it requires frequent manual updates (e.g. when new GHC versions are released)</li>
<li>it requires to learn the generation script/format and keep up with updates</li>
<li>the output is often awful to read, verbose and lacks commentary</li>
</ul>
<p>Instead, you can dynamically get the latest, say, 5 major GHC versions in GHCup via:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="ex">ghcup</span> -s GHCupURL list -r -t ghc -c available <span class="kw">|</span> <span class="fu">awk</span> <span class="st">&#39;{ print $2 }&#39;</span> <span class="kw">|</span> <span class="fu">awk</span> -F <span class="st">&#39;.&#39;</span> <span class="st">&#39;{ print $1 &quot;.&quot; $2}&#39;</span> <span class="kw">|</span> <span class="fu">sort</span> -Vu <span class="kw">|</span> <span class="fu">tail</span> -5</span></code></pre></div>
<p>That’s nice… we could just be done now, but I also want to make sure GHCup’s <code>recommended</code> and <code>latest</code> versions are always tested. That requires us to resolve the major versions (X.Y) back to the latest full version (X.Y.Z) and also avoid duplicates due to adding <code>recommended</code> and <code>latest</code>.</p>
<p>Here’s the full code:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="co"># store all available ghc versions</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a><span class="va">all=</span><span class="st">&quot;</span><span class="va">$(</span><span class="ex">ghcup</span> -s GHCupURL list -r -t ghc -c available<span class="va">)</span><span class="st">&quot;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a><span class="co"># get the recommended version</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true"></a><span class="va">rec=$(</span><span class="bu">echo</span> <span class="st">&quot;</span><span class="va">${all}</span><span class="st">&quot;</span>         <span class="kw">|</span> <span class="fu">grep</span> recommended <span class="kw">|</span> <span class="fu">awk</span> <span class="st">&#39;{ print $2 }&#39;</span><span class="va">)</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true"></a><span class="co"># get the latest version</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true"></a><span class="va">latest=$(</span><span class="bu">echo</span> <span class="st">&quot;</span><span class="va">${all}</span><span class="st">&quot;</span>      <span class="kw">|</span> <span class="fu">grep</span> latest      <span class="kw">|</span> <span class="fu">awk</span> <span class="st">&#39;{ print $2 }&#39;</span><span class="va">)</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true"></a><span class="co"># get the last 5 major versions</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true"></a><span class="va">other_major=$(</span><span class="bu">echo</span> <span class="st">&quot;</span><span class="va">${all}</span><span class="st">&quot;</span>                    <span class="kw">|</span> <span class="fu">awk</span> <span class="st">&#39;{ print $2 }&#39;</span> <span class="kw">|</span> <span class="fu">awk</span> -F <span class="st">&#39;.&#39;</span> <span class="st">&#39;{ print $1 &quot;.&quot; $2}&#39;</span> <span class="kw">|</span> <span class="fu">sort</span> -Vu <span class="kw">|</span> <span class="fu">tail</span> -5<span class="va">)</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true"></a><span class="co"># resolve the major versions back to their respective latest version (e.g. 9.6 -&gt; 9.6.7)</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true"></a><span class="va">other=$(</span><span class="kw">for</span> <span class="ex">v</span> in <span class="va">$other_major</span> <span class="kw">;</span> <span class="kw">do</span> <span class="va">point_releases=$(</span><span class="bu">echo</span> <span class="st">&quot;</span><span class="va">$all</span><span class="st">&quot;</span> <span class="kw">|</span> <span class="fu">awk</span> <span class="st">&#39;{ print $2 }&#39;</span> <span class="kw">|</span> <span class="fu">grep</span> --color=never <span class="st">&quot;^</span><span class="va">$v</span><span class="st">.&quot;</span><span class="va">)</span> ; <span class="bu">echo</span> <span class="st">&quot;</span><span class="va">${point_releases}</span><span class="st">&quot;</span> <span class="kw">|</span> <span class="fu">tail</span> -n1  <span class="kw">;</span> <span class="kw">done</span><span class="va">)</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true"></a><span class="co"># sort and deduplicate</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true"></a><span class="va">selected=$(</span><span class="bu">echo</span> -n <span class="va">${rec}</span> <span class="va">${latest}</span> <span class="va">${other}</span> <span class="kw">|</span> <span class="fu">tr</span> <span class="st">&quot; &quot;</span> <span class="st">&quot;\n&quot;</span>  <span class="kw">|</span> <span class="fu">sort</span> -Vu<span class="va">)</span></span></code></pre></div>
<p>At the time of writing, we’ll get:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a>$ <span class="bu">echo</span> <span class="st">&quot;</span><span class="va">$selected</span><span class="st">&quot;</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="ex">9.6.7</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a><span class="ex">9.8.4</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a><span class="ex">9.10.3</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a><span class="ex">9.12.2</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a><span class="ex">9.14.1</span></span></code></pre></div>
<p>This looks fine. Now the remaining question is how to make this work with GitHub CI matrix. We can easily utilize the <code>GITHUB_OUTPUT</code> feature like so:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a><span class="at">  </span><span class="fu">tool-output</span><span class="kw">:</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a><span class="at">    </span><span class="fu">runs-on</span><span class="kw">:</span><span class="at"> ubuntu-latest</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a><span class="at">    </span><span class="fu">outputs</span><span class="kw">:</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a><span class="at">      </span><span class="fu">ghc_versions</span><span class="kw">:</span><span class="at"> ${{ steps.gen_output.outputs.ghc_versions }}</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true"></a></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> haskell/ghcup-setup@v1</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true"></a></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Generate output</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true"></a><span class="at">      </span><span class="fu">id</span><span class="kw">:</span><span class="at"> gen_output</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true"></a>        all=&quot;$(ghcup -s GHCupURL list -r -t ghc -c available)&quot;</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true"></a>        rec=$(echo &quot;${all}&quot;         | grep recommended | awk &#39;{ print $2 }&#39;)</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true"></a>        latest=$(echo &quot;${all}&quot;      | grep latest      | awk &#39;{ print $2 }&#39;)</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true"></a></span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true"></a>        other_major=$(echo &quot;${all}&quot; | awk &#39;{ print $2 }&#39; | awk -F &#39;.&#39; &#39;{ print $1 &quot;.&quot; $2}&#39; | sort -Vu | tail -5)</span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true"></a>        other=$(for v in $other_major ; do point_releases=$(echo &quot;$all&quot; | awk &#39;{ print $2 }&#39; | grep --color=never &quot;^$v.&quot;) ; echo &quot;${point_releases}&quot; | tail -n1  ; done)</span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true"></a></span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true"></a>        selected=$(echo -n ${rec} ${latest} ${other} | tr &quot; &quot; &quot;\n&quot;  | sort -Vu)</span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true"></a>        selected_json=$(echo -n $selected | jq -c -r -R &#39;split(&quot; &quot;) | [ .[] | if length &gt; 0 then . else empty end ]&#39;)</span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true"></a></span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true"></a>        echo &quot;${selected}&quot;</span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true"></a>        echo &quot;${selected_json}&quot;</span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true"></a></span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true"></a>        echo ghc_versions=&quot;${selected_json}&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;</span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true"></a></span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true"></a><span class="at">  </span><span class="fu">build</span><span class="kw">:</span></span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true"></a><span class="at">    </span><span class="fu">runs-on</span><span class="kw">:</span><span class="at"> ${{ matrix.os }}</span></span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true"></a><span class="at">    </span><span class="fu">needs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">&quot;tool-output&quot;</span><span class="kw">]</span></span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true"></a><span class="at">    </span><span class="fu">strategy</span><span class="kw">:</span></span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true"></a><span class="at">      </span><span class="fu">fail-fast</span><span class="kw">:</span><span class="at"> </span><span class="ch">false</span></span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true"></a><span class="at">      </span><span class="fu">matrix</span><span class="kw">:</span></span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true"></a><span class="at">        </span><span class="fu">os</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at">ubuntu-latest</span><span class="kw">,</span><span class="at"> ubuntu-24.04-arm</span><span class="kw">,</span><span class="at"> macOS-15-intel</span><span class="kw">,</span><span class="at"> macOS-latest</span><span class="kw">,</span><span class="at"> windows-latest</span><span class="kw">]</span></span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true"></a><span class="at">        </span><span class="fu">ghc</span><span class="kw">:</span><span class="at"> ${{ fromJSON(needs.tool-output.outputs.ghc_versions) }}</span></span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> actions/checkout@v4</span></span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true"></a></span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Install dependencies (Ubuntu)</span></span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true"></a><span class="at">      </span><span class="fu">if</span><span class="kw">:</span><span class="at"> runner.os == &#39;Linux&#39;</span></span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true"></a>        sudo apt-get -y update</span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true"></a>        sudo apt-get -y install build-essential curl libffi-dev libgmp-dev libncurses-dev pkg-config</span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true"></a></span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> haskell/ghcup-setup@v1</span></span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true"></a><span class="at">      </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true"></a><span class="at">        </span><span class="fu">ghc</span><span class="kw">:</span><span class="at"> ${{ matrix.ghc }}</span></span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true"></a><span class="at">        </span><span class="fu">cabal</span><span class="kw">:</span><span class="at"> latest</span></span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true"></a></span>
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Build</span></span>
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true"></a>        cabal update</span>
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true"></a>        cabal test --test-show-details=direct all</span></code></pre></div>
<p>Done, now we never have to update our CI manually, just because a new GHC version arrived.</p>
<p>We can also make use of <a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-variables">repository variables</a> to change the number of GHC versions tested on the fly without changing CI configuration, e.g.:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Generate output</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a><span class="at">      </span><span class="fu">id</span><span class="kw">:</span><span class="at"> gen_output</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a>        # ... snip ...</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true"></a>        other_major=$(echo &quot;${all}&quot; | awk &#39;{ print $2 }&#39; | awk -F &#39;.&#39; &#39;{ print $1 &quot;.&quot; $2}&#39; | sort -Vu | tail -${GHC_TEST_NUM:=5})</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true"></a></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true"></a>        # ... snip ...</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true"></a><span class="at">      </span><span class="fu">env</span><span class="kw">:</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true"></a><span class="at">        </span><span class="fu">GHC_TEST_NUM</span><span class="kw">:</span><span class="at"> ${{ vars.GHC_TEST_NUM }}</span></span></code></pre></div>
<h2 id="reacting-to-failures">Reacting to failures</h2>
<p>So what does this mean for a regular project? Your CI will likely suddenly fail some day, because a new GHC release dropped. But that’s exactly what we want. It requires our attention.</p>
<p>To make this even more immediate, it’s good to also have your CI run every night, like so:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true"></a><span class="fu">on</span><span class="kw">:</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a><span class="at">  </span><span class="fu">push</span><span class="kw">:</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true"></a><span class="at">    </span><span class="fu">branches</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at"> master </span><span class="kw">]</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true"></a><span class="at">  </span><span class="fu">pull_request</span><span class="kw">:</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true"></a><span class="at">    </span><span class="fu">branches</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at"> master </span><span class="kw">]</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true"></a><span class="at">  </span><span class="fu">schedule</span><span class="kw">:</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">cron</span><span class="kw">:</span><span class="at"> </span><span class="st">&#39;0 0 * * *&#39;</span></span></code></pre></div>
<p>But we might not want to constantly have CI failures when we’re waiting for some of our dear hackage colleagues to update our dependencies, until our package starts working again. So we want some sort of exclusion list. We can do this dynamically too via <a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-variables">repository variables</a> again. We’ll end up with something like:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Generate output</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true"></a><span class="at">      </span><span class="fu">id</span><span class="kw">:</span><span class="at"> gen_output</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true"></a>        all=&quot;$(ghcup -s GHCupURL list -r -t ghc -c available)&quot;</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true"></a>        rec=$(echo &quot;${all}&quot;         | grep recommended | awk &#39;{ print $2 }&#39;)</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true"></a>        latest=$(echo &quot;${all}&quot;      | grep latest      | awk &#39;{ print $2 }&#39;)</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true"></a></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true"></a>        other_major=$(echo &quot;${all}&quot; | awk &#39;{ print $2 }&#39; | awk -F &#39;.&#39; &#39;{ print $1 &quot;.&quot; $2}&#39; | sort -Vu | tail -${GHC_TEST_NUM:=5})</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true"></a>        other=$(for v in $other_major ; do point_releases=$(echo &quot;$all&quot; | awk &#39;{ print $2 }&#39; | grep --color=never &quot;^$v.&quot;) ; echo &quot;${point_releases}&quot; | tail -n1  ; done)</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true"></a></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true"></a>        selected=$(echo -n ${rec} ${latest} ${other} | tr &quot; &quot; &quot;\n&quot;  | sort -Vu)</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true"></a>        ghc_exclude=( $GHC_EXCLUDE )</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true"></a>        selected_filtered=()</span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true"></a>        for ghc in $selected ; do</span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true"></a>          printf &#39;%s\0&#39; &quot;${ghc_exclude[@]}&quot; | grep --quiet --color=never -F -x -z -- $ghc || selected_filtered+=( $ghc )</span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true"></a>        done</span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true"></a>        unset ghc</span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true"></a>        selected_json=$(echo -n ${selected_filtered[@]} | jq -c -r -R &#39;split(&quot; &quot;) | [ .[] | if length &gt; 0 then . else empty end ]&#39;)</span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true"></a></span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true"></a>        echo &quot;${selected}&quot;</span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true"></a>        echo &quot;${selected_filtered}&quot;</span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true"></a>        echo &quot;${selected_json}&quot;</span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true"></a></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true"></a>        echo ghc_versions=&quot;${selected_json}&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;</span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true"></a><span class="at">      </span><span class="fu">shell</span><span class="kw">:</span><span class="at"> bash</span></span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true"></a><span class="at">      </span><span class="fu">env</span><span class="kw">:</span></span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true"></a><span class="at">        </span><span class="fu">GHC_TEST_NUM</span><span class="kw">:</span><span class="at"> ${{ vars.GHC_TEST_NUM }}</span></span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true"></a><span class="at">        </span><span class="fu">GHC_EXCLUDE</span><span class="kw">:</span><span class="at"> ${{ vars.GHC_EXCLUDE }}</span></span></code></pre></div>
<h2 id="all-put-together">All put together</h2>
<p>At last, all put together, we get:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true"></a><span class="fu">name</span><span class="kw">:</span><span class="at"> Haskell CI</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true"></a><span class="fu">on</span><span class="kw">:</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true"></a><span class="at">  </span><span class="fu">push</span><span class="kw">:</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true"></a><span class="at">    </span><span class="fu">branches</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at"> master </span><span class="kw">]</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true"></a><span class="at">  </span><span class="fu">pull_request</span><span class="kw">:</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true"></a><span class="at">    </span><span class="fu">branches</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at"> master </span><span class="kw">]</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true"></a><span class="at">  </span><span class="fu">schedule</span><span class="kw">:</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">cron</span><span class="kw">:</span><span class="at"> </span><span class="st">&#39;0 0 * * *&#39;</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true"></a></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true"></a><span class="fu">jobs</span><span class="kw">:</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true"></a><span class="at">  </span><span class="fu">tool-output</span><span class="kw">:</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true"></a><span class="at">    </span><span class="fu">runs-on</span><span class="kw">:</span><span class="at"> ubuntu-latest</span></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true"></a><span class="at">    </span><span class="fu">outputs</span><span class="kw">:</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true"></a><span class="at">      </span><span class="fu">ghc_versions</span><span class="kw">:</span><span class="at"> ${{ steps.gen_output.outputs.ghc_versions }}</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true"></a></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true"></a></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> haskell/ghcup-setup@v1</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true"></a></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Generate output</span></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true"></a><span class="at">      </span><span class="fu">id</span><span class="kw">:</span><span class="at"> gen_output</span></span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true"></a>        all=&quot;$(ghcup -s GHCupURL list -r -t ghc -c available)&quot;</span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true"></a>        rec=$(echo &quot;${all}&quot;         | grep recommended | awk &#39;{ print $2 }&#39;)</span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true"></a>        latest=$(echo &quot;${all}&quot;      | grep latest      | awk &#39;{ print $2 }&#39;)</span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true"></a></span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true"></a>        other_major=$(echo &quot;${all}&quot; | awk &#39;{ print $2 }&#39; | awk -F &#39;.&#39; &#39;{ print $1 &quot;.&quot; $2}&#39; | sort -Vu | tail -${GHC_TEST_NUM:=5})</span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true"></a>        other=$(for v in $other_major ; do point_releases=$(echo &quot;$all&quot; | awk &#39;{ print $2 }&#39; | grep --color=never &quot;^$v.&quot;) ; echo &quot;${point_releases}&quot; | tail -n1  ; done)</span>
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true"></a></span>
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true"></a>        selected=$(echo -n ${rec} ${latest} ${other} | tr &quot; &quot; &quot;\n&quot;  | sort -Vu)</span>
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true"></a>        ghc_exclude=( $GHC_EXCLUDE )</span>
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true"></a>        selected_filtered=()</span>
<span id="cb8-34"><a href="#cb8-34" aria-hidden="true"></a>        for ghc in $selected ; do</span>
<span id="cb8-35"><a href="#cb8-35" aria-hidden="true"></a>          printf &#39;%s\0&#39; &quot;${ghc_exclude[@]}&quot; | grep --quiet --color=never -F -x -z -- $ghc || selected_filtered+=( $ghc )</span>
<span id="cb8-36"><a href="#cb8-36" aria-hidden="true"></a>        done</span>
<span id="cb8-37"><a href="#cb8-37" aria-hidden="true"></a>        unset ghc</span>
<span id="cb8-38"><a href="#cb8-38" aria-hidden="true"></a>        selected_json=$(echo -n ${selected_filtered[@]} | jq -c -r -R &#39;split(&quot; &quot;) | [ .[] | if length &gt; 0 then . else empty end ]&#39;)</span>
<span id="cb8-39"><a href="#cb8-39" aria-hidden="true"></a></span>
<span id="cb8-40"><a href="#cb8-40" aria-hidden="true"></a>        echo &quot;${selected}&quot;</span>
<span id="cb8-41"><a href="#cb8-41" aria-hidden="true"></a>        echo &quot;${selected_filtered}&quot;</span>
<span id="cb8-42"><a href="#cb8-42" aria-hidden="true"></a>        echo &quot;${selected_json}&quot;</span>
<span id="cb8-43"><a href="#cb8-43" aria-hidden="true"></a></span>
<span id="cb8-44"><a href="#cb8-44" aria-hidden="true"></a>        echo ghc_versions=&quot;${selected_json}&quot; &gt;&gt; &quot;$GITHUB_OUTPUT&quot;</span>
<span id="cb8-45"><a href="#cb8-45" aria-hidden="true"></a><span class="at">      </span><span class="fu">shell</span><span class="kw">:</span><span class="at"> bash</span></span>
<span id="cb8-46"><a href="#cb8-46" aria-hidden="true"></a><span class="at">      </span><span class="fu">env</span><span class="kw">:</span></span>
<span id="cb8-47"><a href="#cb8-47" aria-hidden="true"></a><span class="at">        </span><span class="fu">GHC_TEST_NUM</span><span class="kw">:</span><span class="at"> ${{ vars.GHC_TEST_NUM }}</span></span>
<span id="cb8-48"><a href="#cb8-48" aria-hidden="true"></a><span class="at">        </span><span class="fu">GHC_EXCLUDE</span><span class="kw">:</span><span class="at"> ${{ vars.GHC_EXCLUDE }}</span></span>
<span id="cb8-49"><a href="#cb8-49" aria-hidden="true"></a></span>
<span id="cb8-50"><a href="#cb8-50" aria-hidden="true"></a><span class="at">  </span><span class="fu">build</span><span class="kw">:</span></span>
<span id="cb8-51"><a href="#cb8-51" aria-hidden="true"></a></span>
<span id="cb8-52"><a href="#cb8-52" aria-hidden="true"></a><span class="at">    </span><span class="fu">runs-on</span><span class="kw">:</span><span class="at"> ${{ matrix.os }}</span></span>
<span id="cb8-53"><a href="#cb8-53" aria-hidden="true"></a><span class="at">    </span><span class="fu">needs</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="st">&quot;tool-output&quot;</span><span class="kw">]</span></span>
<span id="cb8-54"><a href="#cb8-54" aria-hidden="true"></a><span class="at">    </span><span class="fu">strategy</span><span class="kw">:</span></span>
<span id="cb8-55"><a href="#cb8-55" aria-hidden="true"></a><span class="at">      </span><span class="fu">fail-fast</span><span class="kw">:</span><span class="at"> </span><span class="ch">false</span></span>
<span id="cb8-56"><a href="#cb8-56" aria-hidden="true"></a><span class="at">      </span><span class="fu">matrix</span><span class="kw">:</span></span>
<span id="cb8-57"><a href="#cb8-57" aria-hidden="true"></a><span class="at">        </span><span class="fu">os</span><span class="kw">:</span><span class="at"> </span><span class="kw">[</span><span class="at">ubuntu-latest</span><span class="kw">,</span><span class="at"> ubuntu-24.04-arm</span><span class="kw">,</span><span class="at"> macOS-15-intel</span><span class="kw">,</span><span class="at"> macOS-latest</span><span class="kw">,</span><span class="at"> windows-latest</span><span class="kw">]</span></span>
<span id="cb8-58"><a href="#cb8-58" aria-hidden="true"></a><span class="at">        </span><span class="fu">ghc</span><span class="kw">:</span><span class="at"> ${{ fromJSON(needs.tool-output.outputs.ghc_versions) }}</span></span>
<span id="cb8-59"><a href="#cb8-59" aria-hidden="true"></a><span class="at">    </span><span class="fu">steps</span><span class="kw">:</span></span>
<span id="cb8-60"><a href="#cb8-60" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> actions/checkout@v4</span></span>
<span id="cb8-61"><a href="#cb8-61" aria-hidden="true"></a></span>
<span id="cb8-62"><a href="#cb8-62" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Install dependencies (Ubuntu)</span></span>
<span id="cb8-63"><a href="#cb8-63" aria-hidden="true"></a><span class="at">      </span><span class="fu">if</span><span class="kw">:</span><span class="at"> runner.os == &#39;Linux&#39;</span></span>
<span id="cb8-64"><a href="#cb8-64" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb8-65"><a href="#cb8-65" aria-hidden="true"></a>        sudo apt-get -y update</span>
<span id="cb8-66"><a href="#cb8-66" aria-hidden="true"></a>        sudo apt-get -y install build-essential curl libffi-dev libffi8 libgmp-dev libgmp10 libncurses-dev pkg-config libtinfo6 libncurses6</span>
<span id="cb8-67"><a href="#cb8-67" aria-hidden="true"></a></span>
<span id="cb8-68"><a href="#cb8-68" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">uses</span><span class="kw">:</span><span class="at"> haskell/ghcup-setup@v1</span></span>
<span id="cb8-69"><a href="#cb8-69" aria-hidden="true"></a><span class="at">      </span><span class="fu">with</span><span class="kw">:</span></span>
<span id="cb8-70"><a href="#cb8-70" aria-hidden="true"></a><span class="at">        </span><span class="fu">ghc</span><span class="kw">:</span><span class="at"> ${{ matrix.ghc }}</span></span>
<span id="cb8-71"><a href="#cb8-71" aria-hidden="true"></a><span class="at">        </span><span class="fu">cabal</span><span class="kw">:</span><span class="at"> latest</span></span>
<span id="cb8-72"><a href="#cb8-72" aria-hidden="true"></a></span>
<span id="cb8-73"><a href="#cb8-73" aria-hidden="true"></a><span class="at">    </span><span class="kw">-</span><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> Build</span></span>
<span id="cb8-74"><a href="#cb8-74" aria-hidden="true"></a><span class="fu">      run</span><span class="kw">: </span><span class="ch">|</span></span>
<span id="cb8-75"><a href="#cb8-75" aria-hidden="true"></a>        cabal update</span>
<span id="cb8-76"><a href="#cb8-76" aria-hidden="true"></a>        cabal test --test-show-details=direct all</span></code></pre></div>
<p>You can see these dynamic GHC matrices in action here:</p>
<ul>
<li><a href="https://github.com/haskell/filepath/blob/master/.github/workflows/test.yaml">workflow file</a></li>
<li><a href="https://github.com/haskell/filepath/actions/runs/21240352923">CI run</a></li>
</ul>
<h2 id="a-few-notes">A few notes</h2>
<p>You may also notice that we don’t use <a href="https://github.com/haskell-actions/setup">haskell-actions/setup</a> here, but the <a href="https://github.com/haskell/ghcup-setup">ghcup-setup</a> one, which is simpler, more predictable and maintained by GHCup developers.</p>
<h2 id="prior-art">Prior Art</h2>
<p>GitHub runner-images <a href="https://github.com/actions/runner-images/blob/5668be8c2f48215b30abe1f6cc5dd6a827ff411d/images/ubuntu/scripts/build/install-haskell.sh#L26">do something similar</a> when preinstalling GHCup.</p>]]></summary>
</entry>
<entry>
    <title>April fools': GHCup discontinued (or why you should use Nix)</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2025-04-01-use-nix.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2025-04-01-use-nix.html</id>
    <published>2025-04-01T00:00:00Z</published>
    <updated>2025-04-01T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>After working on GHCup for 6 years, I’ve realized that it’s obsolete. Everyone should just use <a href="https://nixos.org/">nix</a>.</p>
<p>Nix is a purely functional package manager with declarative configuration, reproducible builds and high quality packages. In fact, it is so simple that you just have to understand:</p>
<ul>
<li>the nix package manager: <a href="https://nix.dev/manual/nix/2.26/store/">https://nix.dev/manual/nix/2.26/store/</a></li>
<li>the nix language: <a href="https://nix.dev/tutorials/nix-language.html">https://nix.dev/tutorials/nix-language.html</a></li>
<li>how nixpkgs work: <a href="https://nixos.org/manual/nixpkgs/stable/">https://nixos.org/manual/nixpkgs/stable/</a>
<ul>
<li>the straight forward contribution guide: <a href="https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md">https://github.com/NixOS/nixpkgs/blob/master/CONTRIBUTING.md</a></li>
</ul></li>
<li>nix-shell: <a href="https://nix.dev/manual/nix/2.24/command-ref/nix-shell">https://nix.dev/manual/nix/2.24/command-ref/nix-shell</a></li>
<li>nix flakes
<ul>
<li><a href="https://www.tweag.io/blog/2020-05-25-flakes/">https://www.tweag.io/blog/2020-05-25-flakes/</a></li>
<li><a href="https://www.tweag.io/blog/2020-06-25-eval-cache/">https://www.tweag.io/blog/2020-06-25-eval-cache/</a></li>
<li><a href="https://www.tweag.io/blog/2020-07-31-nixos-flakes/">https://www.tweag.io/blog/2020-07-31-nixos-flakes/</a></li>
<li><a href="https://serokell.io/blog/practical-nix-flakes">https://serokell.io/blog/practical-nix-flakes</a></li>
</ul></li>
<li>or an easier alternative to nixpkgs, haskell.nix: <a href="https://input-output-hk.github.io/haskell.nix/tutorials/getting-started">https://input-output-hk.github.io/haskell.nix/tutorials/getting-started</a></li>
<li>you can also start purchasing templates: <a href="https://template.cs-syd.eu/">https://template.cs-syd.eu/</a></li>
</ul>
<p>Here’s an <a href="https://github.com/MangoIV/cabal-audit/blob/main/flake.nix">example</a> of a nix flake:</p>
<pre><code>{
  nixConfig.allow-import-from-derivation = true;
  description = &quot;cabal-audit&#39;s flake&quot;;
  inputs = {
    # flake inputs
    nixpkgs.url = &quot;github:nixos/nixpkgs/nixpkgs-unstable&quot;;

    # flake parts
    parts.url = &quot;github:hercules-ci/flake-parts&quot;;
    pre-commit-hooks.url = &quot;github:cachix/git-hooks.nix&quot;;
    devshell.url = &quot;github:numtide/devshell&quot;;
    # end flake parts
    # end flake inputs
  };
  outputs = inputs:
    inputs.parts.lib.mkFlake {inherit inputs;} {
      systems = [&quot;x86_64-linux&quot; &quot;aarch64-linux&quot; &quot;x86_64-darwin&quot; &quot;aarch64-darwin&quot;];
      imports = [inputs.pre-commit-hooks.flakeModule inputs.devshell.flakeModule];

      perSystem = {
        config,
        pkgs,
        lib,
        ...
      }: let
        hlib = pkgs.haskell.lib.compose;
        hspkgs = pkgs.haskell.packages.ghc98.override {
          overrides = import ./nix/haskell-overlay.nix {inherit hlib;};
        };
      in {
        # this flake module adds two things
        # 1. the pre-commit script which is automatically run when committing
        #    which checks formatting and lints of both Haskell and nix files
        #    the automatically run check can be bypassed with -n or --no-verify
        # 2. an attribute in the checks.&lt;system&gt; attrset which can be run with
        #    nix flake check which checks the same lints as the pre-commit hook
        pre-commit = {
          check.enable = true;
          settings.hooks = {
            cabal-fmt.enable = true;
            fourmolu.enable = true;
            hlint.enable = true;

            alejandra.enable = true;
            statix.enable = true;
            deadnix.enable = true;
          };
        };

        devShells.plain-haskell = hspkgs.callPackage ./nix/haskell-shell.nix {
          inherit (pkgs.haskell.packages.ghc98) haskell-language-server fourmolu;
        };

        # https://flake.parts/options/devshell for more information; one of the advantages is
        # the beautiful menu this provides where one can add commands that are offered and loaded
        # as part of the devShell
        devshells.default = {
          commands = [
            {
              name = &quot;lint&quot;;
              help = &quot;run formatting and linting of haskell and nix files in the entire repository&quot;;
              command = &quot;pre-commit run --all&quot;;
            }
            {
              name = &quot;regen-nix&quot;;
              help = &quot;regenerate nix derivations for haskell packages&quot;;
              command =
                builtins.readFile (lib.getExe config.packages.regen-nix);
            }
          ];
          devshell = {
            name = &quot;cabal-audit&quot;;
            packagesFrom = [config.devShells.plain-haskell];
            packages = [pkgs.cabal2nix pkgs.alejandra];
            startup.pre-commit.text = config.pre-commit.installationScript;
          };
        };

        packages = {
          inherit (hspkgs) cabal-audit;
          inherit (pkgs) groff;
          default = config.packages.cabal-audit;
          cabal-audit-static = pkgs.pkgsStatic.callPackage ./nix/static.nix {};
          regen-nix = pkgs.writeShellApplication {
            name = &quot;regen-cabal-audit-nix&quot;;
            runtimeInputs = [pkgs.cabal2nix pkgs.alejandra];
            text = let
              v = &quot;ef73a3748f31d8df1557546b26d2d587cdacf459&quot;;
              cmd = pkg: &#39;&#39;
                cabal2nix https://github.com/haskell/security-advisories.git \
                  --revision ${v} \
                  --subpath code/${pkg}/ &gt; ./${pkg}.nix
              &#39;&#39;;
            in &#39;&#39;
              pushd &quot;$PRJ_ROOT&quot;/nix
              ${lib.concatStrings (map cmd [&quot;osv&quot; &quot;cvss&quot; &quot;hsec-core&quot; &quot;hsec-tools&quot;])}
              cabal2nix ../. &gt; ./cabal-audit.nix
              alejandra ./.
              popd
            &#39;&#39;;
          };
        };
      };
    };
}</code></pre>
<p>I’m sure you’ll get it after going through the documentation above.</p>
<p>To summarize, the benefits are:</p>
<ul>
<li>concise documentation</li>
<li>easy to understand concepts</li>
<li>ecosystem always moves in one direction</li>
<li>space efficient</li>
<li>great error messages and ways to debug</li>
<li>follows KISS</li>
<li>follows unix (do one thing and do it well)</li>
</ul>
<p>As such, GHCup will now be on life support for at least 6 months before we pull the plug. Adjust your CI accordingly.</p>]]></summary>
</entry>
<entry>
    <title>The ultimate guide to Haskell Strings</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2024-05-07-ultimate-string-guide.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2024-05-07-ultimate-string-guide.html</id>
    <published>2024-05-07T00:00:00Z</published>
    <updated>2024-05-07T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>This guide is aimed at Haskellers who seek to improve their understanding of String types, be it beginners or seasoned developers. It is also meant to be a quick reference/cheat sheet for deciding which string type to use in a given situation.</p>
<h2 id="toc">TOC</h2>
<ul>
<li><a href="#motivation">Motivation</a></li>
<li><a href="#string-in-prelude">String in Prelude</a></li>
<li><a href="#unicode">Unicode</a>
<ul>
<li><a href="#unicode-code-point">Unicode Code Point</a></li>
<li><a href="#utf-32">UTF-32</a></li>
<li><a href="#utf-16">UTF-16</a></li>
<li><a href="#unicode-scalar-values">Unicode Scalar Values</a></li>
<li><a href="#utf-8">UTF-8</a></li>
<li><a href="#unicode-summary">Unicode summary</a></li>
</ul></li>
<li><a href="#back-to-haskell-string-type">Back to Haskell String type</a></li>
<li><a href="#string-types">String types</a>
<ul>
<li><a href="#text">Text</a></li>
<li><a href="#shorttext">ShortText</a></li>
<li><a href="#bytestring">ByteString</a></li>
<li><a href="#shortbytestring">ShortByteString</a></li>
<li><a href="#bytes">Bytes</a></li>
<li><a href="#osstring-posixstring-and-windowsstring">OsString, PosixString and WindowsString</a></li>
<li><a href="#ospath-posixpath-and-windowspath">OsPath, PosixPath and WindowsPath</a></li>
<li><a href="#cstring-and-cstringlen">CString and CStringLen</a></li>
<li><a href="#filepath">FilePath</a></li>
</ul></li>
<li><a href="#lazy-vs-strict">Lazy vs Strict</a></li>
<li><a href="#slicable-vs-non-slicable">Slicable vs non-slicable</a></li>
<li><a href="#pinned-vs-unpinned">Pinned vs unpinned</a></li>
<li><a href="#string-types-cheat-sheet">String Types Cheat Sheet</a></li>
<li><a href="#construction">Construction</a>
<ul>
<li><a href="#string-literals">String literals</a></li>
<li><a href="#string-classes">String Classes</a></li>
<li><a href="#overloadedstrings">OverloadedStrings</a></li>
<li><a href="#quasiquoters">QuasiQuoters</a></li>
</ul></li>
<li><a href="#conversions">Conversions</a>
<ul>
<li><a href="#from-string-to">From String to…</a></li>
<li><a href="#from-text-to">From Text to…</a></li>
<li><a href="#from-bytestring-to">From ByteString to…</a></li>
<li><a href="#from-shortbytestring-to">From ShortByteString to…</a></li>
<li><a href="#from-osstring-to">From OsString to…</a></li>
<li><a href="#to-json">To JSON</a></li>
</ul></li>
<li><a href="#a-word-on-lazy-io">A word on lazy IO</a></li>
<li><a href="#streaming">Streaming</a></li>
<li><a href="#a-note-on-filepaths">A note on FilePaths</a></li>
<li><a href="#reflection">Reflection</a>
<ul>
<li><a href="#what-we-should-know">What we should know</a></li>
<li><a href="#too-many-strings">Too many Strings</a></li>
<li><a href="#what-are-we-missing">What are we missing</a></li>
</ul></li>
<li><a href="#special-thanks-to">Special thanks to</a></li>
<li><a href="#links-and-relevant-stuff">Links and relevant stuff</a></li>
</ul>
<h2 id="motivation">Motivation</h2>
<p>In 2022 I implemented the <a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">Abstract FilePath proposal</a>, which lead to several new String types, such as <code>OsString</code>.</p>
<p>At the time of writing, I’m also serving on the <a href="https://github.com/haskell/core-libraries-committee">Core Libraries Committee</a>, which oversees the base API. In the context of base, there have been recurring discussions about String types, e.g.:</p>
<ul>
<li><a href="https://discourse.haskell.org/t/informal-discussion-about-the-progression-of-base/6439/153">https://discourse.haskell.org/t/informal-discussion-about-the-progression-of-base/6439/153</a></li>
</ul>
<p>When discussing this topic with other Haskellers, I realized it can indeed be quite confusing and we don’t have comprehensive, over-arching documentation. After all, there is no equivalent of <a href="https://doc.rust-lang.org/stable/book/">The Rust book</a>.</p>
<p>I hope this blog post can fill some of the documentation gaps and also explain the newly introduced types and why I think that <strong>we don’t have too many String types</strong>.</p>
<h2 id="string-in-prelude">String in Prelude</h2>
<p>The most widely used String type in Haskell is defined by the Haskell Standard in <a href="https://www.haskell.org/onlinereport/haskell2010/haskellch9.html#x16-1710009">Chapter 9 Standard Prelude</a>:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="co">-- Lists</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a><span class="kw">data</span>  [a]  <span class="ot">=</span>  [] <span class="op">|</span> a <span class="op">:</span> [a]</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a>        <span class="co">-- Not legal Haskell; for illustration only</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a><span class="co">-- Character type</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Char</span> <span class="ot">=</span> <span class="op">...</span> <span class="ch">&#39;a&#39;</span> <span class="op">|</span> <span class="ch">&#39;b&#39;</span> <span class="op">...</span> <span class="co">-- Unicode values</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a><span class="kw">type</span>  <span class="dt">String</span> <span class="ot">=</span> [<span class="dt">Char</span>]</span></code></pre></div>
<p>Since lists are one of the most idiomatic data types in Haskell, this allows us to easily pattern match on strings, because they are just a list of characters. E.g. the following function returns the first character of a string and its remainder or <code>Nothing</code> if the list is empty.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="ot">uncons ::</span> [a] <span class="ot">-&gt;</span> <span class="dt">Maybe</span> (a, [a])</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a>uncons []     <span class="ot">=</span> <span class="dt">Nothing</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true"></a>uncons (x<span class="op">:</span>xs) <span class="ot">=</span> <span class="dt">Just</span> (x, xs)</span></code></pre></div>
<h3 id="char">Char</h3>
<p>If we look closely at the pseudo code definition of <code>Char</code> from the Haskell standard, we realize the comment saying <code>-- Unicode values</code>. This is a bit vague, in fact. If we look at the documentation in <a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/Data-Char.html#t:Char">Data.Char</a> from base, we see that it is actually implemented as a <a href="https://www.unicode.org/glossary/#code_point">Unicode Code Point</a>.</p>
<p>This can be seen by the <a href="https://wiki.haskell.org/Smart_constructors">smart constructor</a> <code>chr</code> as well:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="fu">chr</span><span class="ot"> ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Char</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="fu">chr</span> i<span class="op">@</span>(<span class="dt">I</span><span class="op">#</span> i<span class="op">#</span>)</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a> <span class="op">|</span> isTrue<span class="op">#</span> (int2Word<span class="op">#</span> i<span class="op">#</span> <span class="ot">`leWord#`</span> <span class="bn">0x10FFFF</span><span class="op">##</span>) <span class="ot">=</span> <span class="dt">C</span><span class="op">#</span> (chr<span class="op">#</span> i<span class="op">#</span>)</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a> <span class="op">|</span> <span class="fu">otherwise</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a>    <span class="ot">=</span> errorWithoutStackTrace (<span class="st">&quot;Prelude.chr: bad argument: &quot;</span> <span class="op">++</span> showSignedInt (<span class="dt">I</span><span class="op">#</span> <span class="dv">9</span><span class="op">#</span>) i <span class="st">&quot;&quot;</span>)</span></code></pre></div>
<p>So <code>Char</code> is basically just an <code>Int</code> with an upper bound on <code>0x10FFFF</code>. In order to understand this, we actually have to take a short dive into Unicode.</p>
<h2 id="unicode">Unicode</h2>
<p>The Unicode Standard is a standard to identify and encode visible “characters” that comprise “text”, supporting all of the world’s major writing systems.</p>
<p>The exact terminology can be very confusing. We’ll focus only on a couple of core concepts. If you want to read up yourself on the standard, here are some pointers:</p>
<ul>
<li>the actual standard: <a href="https://www.unicode.org/versions/latest/">https://www.unicode.org/versions/latest/</a></li>
<li>Wikipedia Unicode article: <a href="https://en.wikipedia.org/wiki/Unicode">https://en.wikipedia.org/wiki/Unicode</a></li>
<li>brief Unicode introduction for JavaScript developers: <a href="https://exploringjs.com/impatient-js/ch_unicode.html">https://exploringjs.com/impatient-js/ch_unicode.html</a></li>
</ul>
<p>The goal of Unicode is to be universal, efficient and unambiguous. In order to achieve that, it needs:</p>
<ul>
<li>a character encoding: translating e.g. <code>a</code> or <code>쟬</code> to something unambiguous</li>
<li>a text encoding: translating a sequence of characters into an efficient byte format</li>
</ul>
<p>The term “character” is quite overloaded and we will go through different definitions along the way.</p>
<h3 id="unicode-code-point">Unicode Code Point</h3>
<p>Unicode Code Points are a way of encoding a single character through numerical values. It ranges from the hexadecimal values 0 to 10FFFF, which we saw before in the definition of <code>chr :: Int -&gt; Char</code>. The formal notation of code points is <code>U+0000</code> to <code>U+10FFFF</code>.</p>
<p>It is essentially a static mapping, e.g.:</p>
<table>
<thead>
<tr class="header">
<th>character</th>
<th>code point</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>a</td>
<td>U+0061</td>
</tr>
<tr class="even">
<td>b</td>
<td>U+0062</td>
</tr>
<tr class="odd">
<td>쟬</td>
<td>U+C7EC</td>
</tr>
<tr class="even">
<td>🇯</td>
<td>U+1F1EF</td>
</tr>
<tr class="odd">
<td>🇵</td>
<td>U+1F1F5</td>
</tr>
<tr class="even">
<td>🇯🇵</td>
<td>U+1F1EF, U+1F1F5</td>
</tr>
</tbody>
</table>
<p>This allows us a couple of observations:</p>
<ul>
<li>the hex values <code>61</code> for <code>a</code> and <code>62</code> for <code>b</code> correspond to the <a href="https://en.wikipedia.org/wiki/ASCII">ASCII character set</a> (cool)</li>
<li>it can express Chinese and other non-Latin characters</li>
<li>some “characters” (in this case actually emoji) are expressed by multiple code points, such as 🇯🇵</li>
</ul>
<p>However, this is just a mapping for a single character. In order to efficiently represent a whole text, several Unicode Transformation Formats were developed, most notably:</p>
<ul>
<li>UTF-32</li>
<li>UTF-16</li>
<li>UTF-8</li>
</ul>
<p>Such transformation formats are necessary to understand code point boundaries in a sequence of bytes and make searching and splitting feasible. UTF-16 and UTF-8 are also optimized for size.</p>
<h3 id="utf-32">UTF-32</h3>
<p>The most simple encoding for text would be to just use the code point values. The issue with this is that the maximum code point value is <code>U+10FFFF</code>, which only fits into 21 bits.</p>
<p>UTF-32 is a fixed-length encoding that uses 32 bits (four bytes) and as such can hold all possible Unicode values without any actual transformation.</p>
<p>The upside of this is that it’s simple, the downside is that it’s wasting space, because most values don’t need the whole 21 bits (e.g. ASCII just needs 7 bits).</p>
<p>UTF-32 is not ASCII compatible, meaning a program that only understands ASCII won’t accidentally work with UTF-32 text, even if all of the characters used are in the ASCII set (e.g. only Latin characters from <code>[a-zA-Z]</code>).</p>
<h3 id="utf-16">UTF-16</h3>
<p>This is a <a href="https://en.wikipedia.org/wiki/Variable-width_encoding">variable-width character encoding</a>, most notably <a href="https://learn.microsoft.com/en-us/windows/win32/learnwin32/working-with-strings">used on Windows</a>.</p>
<p>Code points from <code>U+0000</code> to <code>U+FFFF</code> are expressed “directly” via 2 bytes (16 bits), with the exception of surrogates, which I will explain later.</p>
<p>Code points from <code>U+10000</code> to <code>U+10FFFF</code> don’t fit into 2 bytes. In order to encode these without being accidentally ambiguous, <strong>surrogates</strong> were introduced (another option would have been magic bits as used by UTF-8, but I guess the format wasn’t designed with extension in mind). These surrogates must always come in pairs (so 4 bytes) and are in the following ranges:</p>
<ul>
<li>low surrogates: <code>U+DC00</code> to <code>U+DFFF</code></li>
<li>high surrogates: <code>U+D800</code> to <code>U+DBFF</code></li>
</ul>
<p>Through bit shuffling, these 2-byte pairs allow to map to values in the <code>U+10000</code> to <code>U+10FFFF</code> range. For the interested reader, the algorithm is as follows (<a href="https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF">cited from Wikipedia</a>):</p>
<blockquote>
<ul>
<li>0x10000 is subtracted from the code point (U), leaving a 20-bit number (U’) in the hex number range 0x00000–0xFFFFF.</li>
<li>The high ten bits (in the range 0x000–0x3FF) are added to 0xD800 to give the first 16-bit code unit or high surrogate (W1), which will be in the range 0xD800–0xDBFF.</li>
<li>The low ten bits (also in the range 0x000–0x3FF) are added to 0xDC00 to give the second 16-bit code unit or low surrogate (W2), which will be in the range 0xDC00–0xDFFF</li>
</ul>
</blockquote>
<p>UTF-16 is not ASCII compatible either. It is more space efficient than UTF-32 though. For some languages, it can even be more space efficient than UTF-8.</p>
<h3 id="unicode-scalar-values">Unicode Scalar Values</h3>
<p>It is important to understand that the Haskell <code>Char</code> type (which is essentially a Code Point) can represent surrogates that are used in UTF-16.</p>
<p>The Unicode standard also defines the concept of <a href="http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#page=35">Unicode Scalar Values</a>:</p>
<blockquote>
<p>Any Unicode code point except high-surrogate and low-surrogate code points. In other words, the ranges of integers 0 to D7FF16 and E00016 to 10FFFF16 inclusive.</p>
</blockquote>
<p>So, code point without surrogates. This will become relevant for UTF-8.</p>
<h3 id="utf-8">UTF-8</h3>
<p>This is similar to UTF-16 a variable-width character encoding. It’s often used in web APIs (most notably JSON) and is often the default on Unix systems.</p>
<p>Here, a Unicode Code Point is represented by a sequence of bytes. The number of bytes required depends on the range of the code point and varies between 1 and 4 bytes. The whole bit conversion between code point and UTF-8 is illustrated in the following table (adopted from <a href="https://en.wikipedia.org/wiki/UTF-8#Encoding">Wikipedia</a>):</p>
<table>
<thead>
<tr class="header">
<th>First code point</th>
<th>Last code point</th>
<th>Byte 1</th>
<th>Byte 2</th>
<th>Byte 3</th>
<th>Byte 4</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>U+00<span style="color:red">0</span><span style="color:purple">0</span></td>
<td>U+00<span style="color:red">7</span><span style="color:purple">F</span></td>
<td>0<span style="color:red">xxx</span><span style="color:purple">xxxx</span></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr class="even">
<td>U+0<span style="color:green">0</span><span style="color:red">8</span><span style="color:purple">0</span></td>
<td>U+0<span style="color:green">7</span><span style="color:red">F</span><span style="color:purple">F</span></td>
<td>110<span style="color:green">xxx</span><span style="color:red">xx</span></td>
<td>10<span style="color:red">xx</span><span style="color:purple">xxxx</span></td>
<td></td>
<td></td>
</tr>
<tr class="odd">
<td>U+<span style="color:blue">0</span><span style="color:green">8</span><span style="color:red">0</span><span style="color:purple">0</span></td>
<td>U+<span style="color:blue">F</span><span style="color:green">F</span><span style="color:red">F</span><span style="color:purple">F</span></td>
<td>1110<span style="color:blue">xxxx</span></td>
<td>10<span style="color:green">xxxx</span><span style="color:red">xx</span></td>
<td>10<span style="color:red">xx</span><span style="color:purple">xxxx</span></td>
<td></td>
</tr>
<tr class="even">
<td>U+<span style="color:crimson">0</span><span style="color:orange">1</span><span style="color:blue">0</span><span style="color:green">0</span><span style="color:red">0</span><span style="color:purple">0</span></td>
<td>U+<span style="color:crimson">1</span><span style="color:orange">0</span><span style="color:blue">F</span><span style="color:green">F</span><span style="color:red">F</span><span style="color:purple">F</span></td>
<td>11110<span style="color:crimson">x</span><span style="color:orange">xx</span></td>
<td>10<span style="color:orange">xx</span><span style="color:blue">xxxx</span></td>
<td>10<span style="color:green">xxxx</span><span style="color:red">xx</span></td>
<td>10<span style="color:red">xx</span><span style="color:purple">xxxx</span></td>
</tr>
</tbody>
</table>
<p>Here we see a different technique than surrogates. UTF-8 uses magic bits in the first byte to signal how many bytes in total must be read for translating to a code point.</p>
<p>Notable properties of UTF-8 are:</p>
<ul>
<li>it is ASCII backwards compatible: a program written for UTF-8 will also understand plain ASCII encoding</li>
<li>Unicode code points in the surrogate range <code>U+D800</code> to <code>U+DFFF</code> are considered invalid byte sequences
<ul>
<li>as a result: UTF-8 only expresses Unicode Scalar Values</li>
</ul></li>
</ul>
<h3 id="unicode-summary">Unicode summary</h3>
<p>Given the above encodings, let’s have another look at our table from above:</p>
<table>
<thead>
<tr class="header">
<th>character</th>
<th>code point</th>
<th>Hex UTF-8</th>
<th>Hex UTF-16</th>
<th>Hex UTF-32</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>a</td>
<td>U+0061</td>
<td>61</td>
<td>0061</td>
<td>00000061</td>
</tr>
<tr class="even">
<td>b</td>
<td>U+0062</td>
<td>62</td>
<td>0062</td>
<td>00000062</td>
</tr>
<tr class="odd">
<td>쟬</td>
<td>U+C7EC</td>
<td>ec 9f ac</td>
<td>c7ec</td>
<td>0000c7ec</td>
</tr>
<tr class="even">
<td>🇯</td>
<td>U+1F1EF</td>
<td>f0 9f 87 af</td>
<td>d83c ddef</td>
<td>0001f1ef</td>
</tr>
<tr class="odd">
<td>🇵</td>
<td>U+1F1F5</td>
<td>f0 9f 87 b5</td>
<td>d83c ddf5</td>
<td>0001f1f5</td>
</tr>
<tr class="even">
<td>🇯🇵</td>
<td>U+1F1EF, U+1F1F5</td>
<td>f0 9f 87 af, f0 9f 87 b5</td>
<td>d83c ddef, d83c ddf5</td>
<td>0001f1ef, 0001f1f5</td>
</tr>
</tbody>
</table>
<p>The interested reader is welcome to verify those values (at least for UTF-8 and UTF-16).</p>
<p>We now understand:</p>
<ul>
<li>the character encoding is the mapping of code points to visible characters</li>
<li>UTF-8, UTF-16 and UTF-32 are text encodings with different trade offs</li>
<li>surrogates are a special case for UTF-16 (<code>Unicode Scalar Values = Unicode Code Points - surrotages</code>)</li>
</ul>
<p>Going back to the definition of “character”, we now see the confusion:</p>
<ul>
<li>a surrogate can hardly be a visible character</li>
<li>the visible character 🇯🇵 needs two code points to be expressed (and there are many others)</li>
</ul>
<p>This has lead to yet another definition: <strong>“Grapheme Cluster”</strong>. This is specified by the <a href="https://www.unicode.org/reports/tr29/">Unicode Standard Annex #29</a>, which deals with determining boundaries between characters, words and sentences. It is, again, quite technical, but is much closer to “user visible character”.</p>
<h2 id="back-to-haskell-string-type">Back to Haskell String type</h2>
<p>Now that we know what a <strong>Unicode Code Point</strong> is, we also understand that the Haskell String type has essentially no <em>text encoding</em>. It is just a linked list of those code points (a subset of <code>Int</code>, in fact). This can be a nice property, e.g. as an intermediate representation when converting between encodings (say UTF-8 to UTF-16).</p>
<p>However, it is a <strong>questionable default for a String type</strong>, because:</p>
<ul>
<li>it is inefficient for large text (carries the overhead of a linked list with thunks for every <code>Char</code>); the <a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/Data-String.html#t:String">haddock documentation of Data.String</a> goes into more detail</li>
<li>it is often confusing for users who don’t have a good mental model of what a <em>Unicode Code Point</em> is</li>
<li>it causes problems for certain conversions (e.g. <code>String -&gt; Text</code>), because of surrogates (it should have been Unicode Scalar Values instead or maybe even Grapheme Clusters)</li>
</ul>
<p>Unfortunately, since it’s defined by the Haskell Standard and has been around since the beginning of time, we won’t be able to get rid of it ever.</p>
<p>This type should only be used for small little projects, prototypes and hello worlds and maybe intermediate representations in some algorithms.</p>
<p>The <code>Show</code> instance of <code>Char</code>/<code>String</code> will print the Unicode Code Point value as a decimal for non-ASCII ranges:</p>
<pre><code>ghci&gt; &quot;a&quot;
&quot;a&quot;
ghci&gt; &quot;쟬&quot;
&quot;\51180&quot;</code></pre>
<p>Show is for debugging, so that seems fine. However this behavior has been challenged before: <a href="https://github.com/haskell/core-libraries-committee/issues/26">Proposal: <code>showLitChar</code> (and <code>show @Char</code>) shouldn’t escape readable Unicode characters</a>.</p>
<h2 id="string-types">String types</h2>
<p>In this section, we will examine each string like type and what its properties and use cases are. <code>String</code> was already discussed and we don’t recommend it, so it’s omitted here.</p>
<ul>
<li>proper Unicode text
<ul>
<li><a href="https://hackage.haskell.org/package/text-2.1.1/docs/Data-Text.html#t:Text">Text</a> (strict and lazy)</li>
<li><a href="https://hackage.haskell.org/package/text-short-0.1.5/docs/Data-Text-Short.html#t:ShortText">ShortText</a></li>
</ul></li>
<li>byte sequences
<ul>
<li><a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString.html#t:ByteString">ByteString</a> (strict and lazy)</li>
<li><a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString-Short.html#t:ShortByteString">ShortByteString</a></li>
<li><a href="https://hackage.haskell.org/package/byteslice-0.2.13.2/docs/Data-Bytes.html#t:Bytes">Bytes</a></li>
<li><a href="https://hackage.haskell.org/package/byteslice-0.2.13.2/docs/Data-Bytes-Chunks.html#t:Chunks">Chunks</a></li>
</ul></li>
<li>byte sequences dealing with platform API differences
<ul>
<li><a href="https://hackage.haskell.org/package/os-string-2.0.2.1/docs/System-OsString.html#t:OsString">OsString</a></li>
<li><a href="https://hackage.haskell.org/package/os-string-2.0.2.1/docs/System-OsString-Posix.html#t:PosixString">PosixString</a></li>
<li><a href="https://hackage.haskell.org/package/os-string-2.0.2.1/docs/System-OsString-Windows.html#t:WindowsString">WindowsString</a></li>
</ul></li>
<li>FFI types
<ul>
<li><a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/Foreign-C-String.html#t:CString">CString</a></li>
<li><a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/Foreign-C-String.html#t:CStringLen">CStringLen</a></li>
</ul></li>
<li>filepath types (just type synonyms)
<ul>
<li><a href="https://hackage.haskell.org/package/filepath-1.5.2.0/docs/System-FilePath.html#t:FilePath">FilePath</a></li>
<li><a href="https://hackage.haskell.org/package/filepath-1.5.2.0/docs/System-OsPath.html#t:OsPath">OsPath</a></li>
<li><a href="https://hackage.haskell.org/package/filepath-1.5.2.0/docs/System-OsPath-Posix.html#t:PosixPath">PosixPath</a></li>
<li><a href="https://hackage.haskell.org/package/filepath-1.5.2.0/docs/System-OsPath-Windows.html#t:WindowsPath">WindowsPath</a></li>
</ul></li>
</ul>
<p>If we delve more into filepaths, there are actually even more, e.g. strongly typed filepaths. But those are out of scope.</p>
<h3 id="text">Text</h3>
<p>If you are not sure what you need, you most likely want <code>Text</code> from the <a href="https://hackage.haskell.org/package/text">text package</a>, which is shipped with GHC. This type is meant for human readable Unicode text and has all the primitives you need. The API is in fact more complete than the one for <code>String</code>, containing functions like <code>stripPrefix</code> and <code>toLower</code>.</p>
<p>Internally, Text uses a UTF-8 encoded byte array since version 2.0 and UTF-16 before version 2.0. So it is always guaranteed to be valid Unicode.</p>
<p>The current definition for strict <code>Text</code> is (as of <a href="https://hackage.haskell.org/package/text-2.1.1">2.1.1</a>):</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="co">-- | A space efficient, packed, unboxed Unicode text type.</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Text</span> <span class="ot">=</span> <span class="dt">Text</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">A.Array</span> <span class="co">-- ^ bytearray encoded as UTF-8</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span>     <span class="co">-- ^ offset in bytes (not in Char!), pointing to a start of UTF-8 sequence</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span>     <span class="co">-- ^ length in bytes (not in Char!), pointing to an end of UTF-8 sequence</span></span></code></pre></div>
<p>As we can see here, this type allows efficient slicing to avoid unnecessary <code>memcpy</code> for many operations. E.g. <code>init</code> and <code>tail</code> are <em>O(1)</em> time and space. <code>splitAt</code> is <em>O(1)</em> space, but <em>O(n)</em> time, because UTF-8 complicates the offset computation (remember, a Unicode Code Point encoding can be anywhere between 1 and 4 bytes in UTF-8).</p>
<p>We explain more about this later in <a href="#slicable-vs-non-slicable">Slicable vs non-slicable</a>.</p>
<p>The lazy Text variant is as follows:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Text</span> <span class="ot">=</span> <span class="dt">Empty</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a>          <span class="op">|</span> <span class="dt">Chunk</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">T.Text</span> <span class="dt">Text</span></span></code></pre></div>
<p>This has the same structure as a list, and as such can also be potentially streamed in constant space or allow the GC to clean up unused chunks after splitting/slicing.</p>
<p>Text does not allow to represent surrogates. It is a sequence of <a href="http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#page=35">Unicode Scalar Values</a>. Invalid values will be converted to the replacement character <code>U+FFFD</code> silently when using e.g. <code>pack</code>. You might be thinking that’s not a problem… but I have to disappoint you. There is a reason <code>String</code> allows surrogates: <a href="https://peps.python.org/pep-0383/">PEP-383</a>. This is an abomination and base uses it: On Unix, it uses <a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/GHC-IO-Encoding.html#v:getFileSystemEncoding"><code>getFileSystemEncoding</code></a> and <a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/GHC-IO-Encoding.html#v:mkTextEncoding"><code>mkTextEncoding</code></a> to pick a round-trippable encoding for filepaths. E.g. if your locale returns <code>en_US.UTF-8</code> you’ll get <code>UTF-8//ROUNDTRIP</code> <code>TextEncoding</code>, which is based on PEP-383 and invalid bytes get translated to some special representation (lone surrogates) in order to be roundtripped. This has been described in my blog <a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">Fixing ‘FilePath’ in Haskell</a>.</p>
<h4 id="text-summary">Text summary</h4>
<p>Invariants:</p>
<ul>
<li>is always Unicode</li>
<li>never encodes surrogates (uses replacement char <code>U+FFFD</code>)</li>
<li>unpinned memory (can be moved by the GC at any time, see the <a href="#pinned-vs-unpinned">Pinned vs unpinned</a> section)</li>
<li>strict and lazy variants</li>
</ul>
<p>Useful for:</p>
<ul>
<li>anything that fits ASCII or Unicode</li>
<li>large human readable text processing that requires efficient formats</li>
<li>complex Unicode handling via advanced libraries such as <a href="https://hackage.haskell.org/package/text-icu">text-icu</a></li>
<li>quite efficient slicing</li>
</ul>
<p>Not so useful for:</p>
<ul>
<li>dealing with C FFI</li>
<li>trying to store or deal with non-Unicode encodings</li>
<li>dealing with filepaths</li>
<li>lots of small Unicode texts</li>
</ul>
<p>Lazy variants are useful for streaming and incremental processing, as the strict variant requires the whole content to be in memory.</p>
<h3 id="shorttext">ShortText</h3>
<p>This is an alternative Unicode text type that is meant for lots of small text sequences. It is part of the <a href="https://hackage.haskell.org/package/text-short">text-short</a> package. The definition is as follows:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">ShortText</span> <span class="ot">=</span> <span class="dt">ShortText</span> <span class="dt">ShortByteString</span></span></code></pre></div>
<p>So there is no length or offset field. This means it has all the same properties as an unpinned <code>ShortByteString</code>, except that the data is guaranteed to be valid UTF-8.</p>
<h4 id="shorttext-summary">ShortText summary</h4>
<p>Invariants:</p>
<ul>
<li>is always Unicode</li>
<li>never encodes surrogates (uses replacement char <code>U+FFFD</code>)</li>
<li>unpinned memory (can be moved by the GC at any time)</li>
<li>strict</li>
</ul>
<p>Useful for:</p>
<ul>
<li>anything that fits ASCII or Unicode</li>
<li>lots of small text sequences</li>
</ul>
<p>Not so useful for:</p>
<ul>
<li>using with <code>text-icu</code> package, which expects <code>Text</code></li>
<li>efficient slicing</li>
<li>dealing with C FFI</li>
<li>trying to store or deal with non-Unicode encodings</li>
<li>dealing with filepaths</li>
</ul>
<h3 id="bytestring">ByteString</h3>
<p>This is a low level type from the <a href="https://hackage.haskell.org/package/bytestring">bytestring</a> package, shipped with GHC. It is just a sequence of bytes and carries no encoding information. It uses <strong>pinned memory</strong> (see <a href="#pinned-vs-unpinned">Pinned vs unpinned</a> section). As such, it doesn’t require copying when dealing with the FFI. It is also often more desirable when interacting with FFI, see the GHC user guide:</p>
<ul>
<li><a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/ffi.html#ghc-differences-to-the-ffi-chapter">GHC differences to the FFI Chapter</a></li>
<li><a href="https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/ffi.html#ghc-extensions-to-the-ffi-chapter">GHC extensions to the FFI Chapter</a></li>
</ul>
<p>ByteString is quite efficient and has a large API, but (obviously) lacks text processing facilities, because it has no knowledge of Unicode (or other textual formats). Most operations work on <code>Word8</code> boundaries.</p>
<p>The definition for strict ByteString is (as of <a href="https://hackage.haskell.org/package/bytestring-0.12.1.0">0.12.1.0</a>):</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">ByteString</span> <span class="ot">=</span> <span class="dt">BS</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span>(<span class="dt">ForeignPtr</span> <span class="dt">Word8</span>) <span class="co">-- payload</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a>                     <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span>                <span class="co">-- length</span></span></code></pre></div>
<p>This allows, similar to Text, slicing without copying memory (through pointer arithmetic and the length field). Since we’re not dealing with Unicode, but just <code>Word8</code> boundaries, operations like <code>splitAt</code> are <em>O(1)</em> time and space. We don’t need an offset field, because we can just advance the pointer instead.</p>
<p>And the lazy counterpart, which looks similar to lazy Text:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">ByteString</span> <span class="ot">=</span> <span class="dt">Empty</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true"></a>                <span class="op">|</span> <span class="dt">Chunk</span>  <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">S.StrictByteString</span> <span class="dt">ByteString</span></span></code></pre></div>
<p>There is an API variant <a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString-Char8.html">Data.ByteString.Char8</a>, which allows operations to work on <code>Char</code> boundaries. However, it can be misleading to newcomers, because it actually truncates all Chars to 8 bits. <strong>You should avoid this, unless you know what you are doing.</strong> It is more likely that you are looking for decoding libraries, where you can specify which encoding to use, e.g. <a href="https://hackage.haskell.org/package/bytestring-encoding-0.1.2.0/docs/Data-ByteString-Encoding.html#v:decode">bytestring-encoding</a>.</p>
<p>It also has to be noted that pinned memory can cause memory fragmentation for lots of small ByteStrings (this is also discussed in <a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">Fixing ‘FilePath’ in Haskell</a>). An alternative type is <code>ShortByteString</code>, which will be discussed next.</p>
<h4 id="bytestring-summary">ByteString summary</h4>
<p>Invariants:</p>
<ul>
<li>pinned memory</li>
<li>strict and lazy variants</li>
</ul>
<p>Useful for:</p>
<ul>
<li>large data</li>
<li>very efficient slicing</li>
<li>dealing with raw bytes (e.g. web servers)</li>
<li>dealing with C FFI</li>
<li>storing non-Unicode encodings e.g. via a newtype wrapper</li>
<li>fast parsers, see the excellent blog post from Chris Done on <a href="https://chrisdone.com/posts/fast-haskell-c-parsing-xml/">Fast Haskell: Competing with C at parsing XML</a></li>
</ul>
<p>Not so useful for:</p>
<ul>
<li>dealing with Unicode or human readable text</li>
<li>dealing with lots of small byte sequences</li>
</ul>
<p>Lazy variants, again, are useful for streaming and incremental processing, as the strict variant requires the whole content to be in memory.</p>
<h3 id="shortbytestring">ShortByteString</h3>
<p>This type is from the bytestring package as well and lives under <a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString-Short.html">Data.ByteString.Short</a>.</p>
<p>It has the same API as <code>ByteString</code> since <a href="https://hackage.haskell.org/package/bytestring-0.11.3.0/changelog">0.11.3.0</a>, so can be used as a drop-in replacement. The main difference is that it is usually backed by <em>unpinned memory</em>, so causes no heap fragmentation. It is possible to construct it pinned via internal API, but slicing operations like <code>splitAt</code> will return unpinned byte strings.</p>
<p>The definition as of <a href="https://hackage.haskell.org/package/bytestring-0.12.1.0">0.12.1.0</a> is:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">ShortByteString</span> <span class="ot">=</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true"></a>  <span class="dt">ShortByteString</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true"></a>  {<span class="ot"> unShortByteString ::</span> <span class="dt">ByteArray</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true"></a>  }</span></code></pre></div>
<p>This makes it suitable for things like Unix filepaths. But we will explore better filepath types later.</p>
<p>The name is maybe a little bit misleading. It can very well be used for large data as well, if you don’t mind its strictness (the whole content is always in memory). <strong>However, this type does not allow slicing</strong>, unlike <code>Text</code> and <code>ByteString</code>, and so a lot of operations cause <code>memcpy</code>. This however has the advantage that we save at least 2 words compared to e.g. <code>Text</code>, because we don’t need an offset or length field.</p>
<p>If you want a similar type, but with slicing capability, use <a href="#bytes">Bytes</a>.</p>
<p>Interfacing with C FFI triggers memory copy as well, because we need pinned memory.</p>
<p>There is no lazy variant.</p>
<h4 id="shortbytestring-summary">ShortByteString summary</h4>
<p>Invariants:</p>
<ul>
<li>unpinned memory (when using the default API)</li>
<li>always strict</li>
</ul>
<p>Useful for:</p>
<ul>
<li>lots of small to medium sized byte sequences</li>
<li>large data, if strictness is desired and efficient slicing not needed</li>
<li>dealing with C FFI (although it incurs <code>memcpy</code>)</li>
<li>storing non-Unicode encodings e.g. via a newtype wrapper</li>
</ul>
<p>Not so useful for:</p>
<ul>
<li>dealing with Unicode or human readable text</li>
<li>fast parsers, because no lazy variant and no efficient slicing</li>
</ul>
<h3 id="bytes">Bytes</h3>
<p>This type is from the <code>byteslice</code> package and lives under <a href="https://hackage.haskell.org/package/byteslice-0.2.13.2/docs/Data-Bytes.html">Data.Bytes</a>. It is not shipped by GHC.</p>
<p>It is a essentially a <code>ShortByteString</code> with 0-copy slicing (<code>init</code>, <code>splitAt</code> etc.). It can be constructed as a pinned or unpinned byte sequence and all the usual operations for it will maintain that invariant.</p>
<p>The definition as of <a href="https://hackage.haskell.org/package/byteslice-0.2.13.2">0.2.13.2</a> is:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Bytes</span> <span class="ot">=</span> <span class="dt">Bytes</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true"></a>  {<span class="ot"> array ::</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">ByteArray</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true"></a>  ,<span class="ot"> offset ::</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true"></a>  ,<span class="ot"> length ::</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true"></a>  }</span></code></pre></div>
<p>This is exactly the same definition as the <code>Text</code> type. But it does not maintain UTF-8. It uses <code>ByteArray</code> like <code>ShortByteString</code> does. Compared to <code>ShortByteString</code> however, we have three words more memory overhead.</p>
<p>The API allows to convert to <code>ByteString</code> and <code>ShortByteString</code>. Depending on whether it was pinned or unpinned, sliced or unsliced, those may be 0-copy operations as well.</p>
<p>There’s another variant called <code>Chunks</code> in <a href="https://hackage.haskell.org/package/byteslice-0.2.13.2/docs/Data-Bytes-Chunks.html">Data.Bytes.Chunks</a>:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Chunks</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true"></a>  <span class="ot">=</span> <span class="dt">ChunksCons</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Bytes</span> <span class="op">!</span><span class="dt">Chunks</span></span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true"></a>  <span class="op">|</span> <span class="dt">ChunksNil</span></span></code></pre></div>
<p>Although This is quite similar to how lazy <code>Text</code> is defined, this type is not lazy at all. It has <a href="https://downloads.haskell.org/ghc/9.6.5/docs/users_guide/exts/strict.html">bang patterns</a> on both the value and the recursion, so it is spine-strict.</p>
<p>The only real use case the <code>Chunk</code> type has is when you want to avoid the overhead of constant appending of <code>ByteArray</code>s, because you’re e.g. reading a file incrementally.</p>
<h4 id="bytes-summary">Bytes summary</h4>
<p>Invariants:</p>
<ul>
<li>can be both pinned or unpinned</li>
<li>is always strict</li>
</ul>
<p>Useful for:</p>
<ul>
<li>when you want an unpinned strict ByteString…</li>
<li>or a slicable ShortByteString</li>
<li>dealing with C FFI</li>
<li>parsers, if we don’t mind strictness</li>
</ul>
<p>Not so useful for:</p>
<ul>
<li>dealing with Unicode or human readable text</li>
</ul>
<h3 id="osstring-posixstring-and-windowsstring">OsString, PosixString and WindowsString</h3>
<p>These are relatively new types, which were first added to <a href="https://hackage.haskell.org/package/filepath-1.4.100.0/changelog">filepath-1.4.100.0</a> as part of a user-space implementation of the <a href="https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path">Abstract FilePath Proposal</a>. More details <a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">here</a>.</p>
<p>Starting with filepath-1.5.0.0, the types were moved to a new home in the <a href="https://hackage.haskell.org/package/os-string">os-string</a> package.</p>
<p>These types are meant to abstract over platform differences and their encodings when dealing with operating system API. It is similar to the rust type <a href="https://doc.rust-lang.org/std/ffi/struct.OsString.html">OsString</a>, but the implementation is quite different.</p>
<p>Simplified, the Haskell definitions are:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true"></a><span class="co">-- | Commonly used Windows string as wide character bytes.</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">WindowsString</span> <span class="ot">=</span> <span class="dt">WindowsString</span> <span class="dt">ShortByteString</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true"></a></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true"></a><span class="co">-- | Commonly used Posix string as uninterpreted @char[]@ array.</span></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">PosixString</span> <span class="ot">=</span> <span class="dt">PosixString</span> <span class="dt">ShortByteString</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true"></a></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true"></a><span class="co">-- | Newtype representing short operating system specific strings.</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true"></a><span class="co">--</span></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true"></a><span class="co">-- Internally this is either &#39;WindowsString&#39; or &#39;PosixString&#39;,</span></span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true"></a><span class="co">-- depending on the platform. Both use unpinned</span></span>
<span id="cb13-11"><a href="#cb13-11" aria-hidden="true"></a><span class="co">-- &#39;ShortByteString&#39; for efficiency.</span></span>
<span id="cb13-12"><a href="#cb13-12" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">OsString</span> <span class="ot">=</span> <span class="dt">OsString</span></span>
<span id="cb13-13"><a href="#cb13-13" aria-hidden="true"></a><span class="pp">#if defined(mingw32_HOST_OS)</span></span>
<span id="cb13-14"><a href="#cb13-14" aria-hidden="true"></a>  <span class="dt">WindowsString</span></span>
<span id="cb13-15"><a href="#cb13-15" aria-hidden="true"></a><span class="pp">#else</span></span>
<span id="cb13-16"><a href="#cb13-16" aria-hidden="true"></a>  <span class="dt">PosixString</span></span>
<span id="cb13-17"><a href="#cb13-17" aria-hidden="true"></a><span class="pp">#endif</span></span></code></pre></div>
<p>As we can see, on Unix, we’re basically dealing with <code>Word8</code> sequences (<code>char[]</code>), but on Windows, we’re dealing with <code>Word16</code> sequences (<code>wchar_t*</code>).</p>
<p>The constructors are internal and it is impossible to pattern match on the wrong platform in <code>OsString</code>, due to the CPP.</p>
<p>OsString provides a rich API just like ByteString.</p>
<p>This allows packages like <code>unix</code> and <code>Win32</code> to provide alternatives to <code>String</code>, where the bytes that are received from operating system API is not transformed, decoded or otherwise roundtripped. It is <strong>unchanged</strong>. E.g.:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/unix-2.8.5.1/docs/System-Posix-IO-PosixString.html#v:openFd">System.Posix.IO.PosixString.openFd</a></li>
<li><a href="https://github.com/haskell/win32/blob/350ebd43f9a8d9e1ca767b0000f95bdfb42a5471/System/Win32/WindowsString/File.hsc#L139">System.Win32.WindowsString.File.createFile</a></li>
</ul>
<p>And at the same time, we are able to write safe, platform agnostic code utilizing <code>OsString</code>. E.g.:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/file-io-0.1.1/docs/System-File-OsPath.html">System.File.OsPath</a></li>
</ul>
<p>This strategy has been used for filepaths, where <code>unix</code> package uses <code>PosixString</code>, <code>Win32</code> package uses <code>WindowsString</code> and the platform agnostic <code>directory</code> and <code>file-io</code> packages use <code>OsString</code>, combining the APIs of Unix and Windows. More information on this with examples and API explanation can be found <a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">here</a>.</p>
<p>It is not restricted to filepaths, but may be extended to dealing with environment variables, program arguments and other bits of operating system API. It is always safer than <code>String</code> and more type safe than <code>ByteString</code>.</p>
<h4 id="osstring-posixstring-and-windowsstring-summary">OsString, PosixString and WindowsString summary</h4>
<p>Invariants:</p>
<ul>
<li>unpinned memory</li>
<li>OsString abstracts over platforms</li>
<li>PosixString is char array</li>
<li>WindowsString is wide char array</li>
</ul>
<p>Useful for:</p>
<ul>
<li>writing type safe operating system APIs
<ul>
<li>while maintaining the original bytes without decoding</li>
<li>abstracting over Unix and Windows</li>
<li>making minimal assumptions on underlying encodings</li>
</ul></li>
</ul>
<p>Not so useful for:</p>
<ul>
<li>very large data</li>
<li>data that is not platform specific or doesn’t originate from operating system API</li>
<li>efficient slicing</li>
</ul>
<h3 id="ospath-posixpath-and-windowspath">OsPath, PosixPath and WindowsPath</h3>
<p>These are equivalent to <code>OsString</code>, <code>PosixString</code> and <code>WindowsString</code> and are part of the <a href="https://hackage.haskell.org/package/filepath">filepath</a> package as of <a href="https://hackage.haskell.org/package/filepath-1.4.100.0">1.4.100.0</a>. They are just type synonyms:</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true"></a><span class="co">-- | FilePath for Windows.</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">WindowsPath</span> <span class="ot">=</span> <span class="dt">WindowsString</span></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true"></a></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true"></a><span class="co">-- | FilePath for posix systems.</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">PosixPath</span> <span class="ot">=</span> <span class="dt">PosixString</span></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true"></a></span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true"></a><span class="co">-- | Abstract filepath, depending on current platform.</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true"></a><span class="co">-- Matching on the wrong constructor is a compile-time error.</span></span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">OsPath</span> <span class="ot">=</span> <span class="dt">OsString</span></span></code></pre></div>
<p>Use them whenever you can with the new filepath API. Refer to the <a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">Fixing Haskell filepaths</a> blog post for more details.</p>
<h3 id="cstring-and-cstringlen">CString and CStringLen</h3>
<p>These are part of <code>base</code> and low-level FFI types.</p>
<p>The definitions are very straight forward:</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true"></a><span class="co">-- | A C string is a reference to an array of C characters terminated by NUL.</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">CString</span>    <span class="ot">=</span> <span class="dt">Ptr</span> <span class="dt">CChar</span></span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true"></a></span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true"></a><span class="co">-- | A string with explicit length information in bytes instead of a</span></span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true"></a><span class="co">-- terminating NUL (allowing NUL characters in the middle of the string).</span></span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">CStringLen</span> <span class="ot">=</span> (<span class="dt">Ptr</span> <span class="dt">CChar</span>, <span class="dt">Int</span>)</span></code></pre></div>
<p>The haddock also explains the expected properties.</p>
<p>As an interesting edge case: if you’re converting from <code>ByteString</code> to <code>CString</code> and happen to have a NUL byte in your ByteString, then <code>useAsCString</code> will over-allocate bytes:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true"></a><span class="ot">useAsCString ::</span> <span class="dt">ByteString</span> <span class="ot">-&gt;</span> (<span class="dt">CString</span> <span class="ot">-&gt;</span> <span class="dt">IO</span> a) <span class="ot">-&gt;</span> <span class="dt">IO</span> a</span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true"></a>useAsCString (<span class="dt">BS</span> fp l) action <span class="ot">=</span></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true"></a>  allocaBytes (l<span class="op">+</span><span class="dv">1</span>) <span class="op">$</span> \buf <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true"></a>    unsafeWithForeignPtr fp <span class="op">$</span> \p <span class="ot">-&gt;</span> copyBytes buf p l</span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true"></a>    pokeByteOff buf l (<span class="dv">0</span><span class="ot">::</span><span class="dt">Word8</span>)</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true"></a>    action (castPtr buf)</span></code></pre></div>
<p>So it can make sense, in some cases, to check your ByteString for NUL bytes.</p>
<p>We won’t dive into the Haskell C FFI, but this is literally the only proper use case. Refer to the <a href="https://en.wikibooks.org/wiki/Haskell/FFI">wikibook article on Haskell FFI</a>.</p>
<h3 id="filepath">FilePath</h3>
<p>This type is a <strong>legacy</strong> filepath type, but is still the most widespread across the ecosystem at the time of writing. It is part of the <a href="https://hackage.haskell.org/package/filepath">filepath</a> package, which is also shipped with GHC.</p>
<p>The definition is:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">FilePath</span> <span class="ot">=</span> <span class="dt">String</span></span></code></pre></div>
<p>This is not a very good choice for filepaths. Use the new <a href="#ospath-posixpath-and-windowspath">OsPath</a> instead.</p>
<h2 id="lazy-vs-strict">Lazy vs Strict</h2>
<p>The properties of lazy vs strict variants for <code>Text</code> and <code>ByteString</code> might already be obvious for many Haskellers:</p>
<ul>
<li><strong>Lazy</strong>:
<ul>
<li>can be streamed and incrementally processed, potentially in constant space</li>
<li>can allow the GC to clean up unused chunks after slicing/splitting</li>
<li>can express infinite data streams</li>
<li>slightly less efficient in terms of time complexity, depending on number of chunks (compared to their strict counterparts)</li>
<li>can work with lazy IO (<a href="#a-word-on-lazy-io">more on that later</a>)</li>
</ul></li>
<li><strong>Strict</strong>:
<ul>
<li>is the most efficient in terms of time complexity</li>
<li>is always forced into memory</li>
<li>has less overhead than lazy types</li>
</ul></li>
</ul>
<p>A lot of time, people use lazy types in conjunction with lazy IO. However, another use case is to use <strong>Builders</strong>. These exist for both Text and ByteString:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString-Builder.html">Data.ByteString.Builder</a></li>
<li><a href="https://hackage.haskell.org/package/text-2.1.1/docs/Data-Text-Lazy-Builder.html">Data.Text.Lazy.Builder</a></li>
</ul>
<p>In general, streaming libraries can be a more elegant and performant alternative to lazy Text/ByteString. We talk about that later in the chapter <a href="#streaming">Streaming</a>. But since much of the ecosystem uses lazy types, these are still relevant for practical purposes.</p>
<h2 id="slicable-vs-non-slicable">Slicable vs non-slicable</h2>
<p>All strings are slicable, but some strings can slice without copying data. E.g. compare <code>Text</code> and <code>ShortText</code>:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Text</span> <span class="ot">=</span> <span class="dt">Text</span></span>
<span id="cb18-2"><a href="#cb18-2" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">A.Array</span> <span class="co">-- ^ bytearray encoded as UTF-8</span></span>
<span id="cb18-3"><a href="#cb18-3" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span>     <span class="co">-- ^ offset in bytes (not in Char!), pointing to a start of UTF-8 sequence</span></span>
<span id="cb18-4"><a href="#cb18-4" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span>     <span class="co">-- ^ length in bytes (not in Char!), pointing to an end of UTF-8 sequence</span></span>
<span id="cb18-5"><a href="#cb18-5" aria-hidden="true"></a></span>
<span id="cb18-6"><a href="#cb18-6" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">ShortText</span> <span class="ot">=</span> <span class="dt">ShortText</span> <span class="dt">ShortByteString</span></span></code></pre></div>
<p>E.g. when we call <code>splitAt</code> on a <code>Text</code> value, we get back two new <code>Text</code> values that just differ in the “offset” and “length” fields, but can point at the same byte array. If we slice a lot, this can save a lot of <code>memcpy</code>, especially on large data.</p>
<p>This means that slicing comes at two costs. First, if we split a text in half, the memory of the original byte array can’t be cleaned up by the GC. We just changed the offset and length fields, nothing else. This can be alleviated by using explicit copy operations when you don’t need the whole data anymore, e.g. via <a href="https://hackage.haskell.org/package/text-2.1.1/docs/Data-Text.html#v:copy">Data.Text.copy</a>.</p>
<p>Second, we carry two unboxed <code>Int</code>s around for the “offset” and “length” fields, which is 2 words “overhead”. For more information on boxed and unboxed types, see the GHC user guide:</p>
<ul>
<li><a href="https://downloads.haskell.org/ghc/9.6.5/docs/users_guide/exts/primitives.html">Unboxed types and primitive operations</a></li>
<li><a href="https://downloads.haskell.org/ghc/9.6.5/docs/users_guide/exts/pragmas.html#unpack-pragma"><code>UNPACK</code> pragma</a></li>
</ul>
<p><code>ShortText</code> in contrast, on e.g. <code>splitAt</code>, will create two new byte arrays and copy the data. Here we’re not only saving two words memory overhead (no offset and length field), but also have a bit less indirection at runtime and a bit less memory pressure (which might be useful to fit into CPU cache) as explained in <a href="https://github.com/hasufell/hasufell.github.io/pull/7#issuecomment-2105160701">this comment</a>.</p>
<p>As such, as the name of the types suggest, a simplified criteria could be:</p>
<ul>
<li>slicable type: if you have large strings or need a lot of slicing</li>
<li>non-slicable type: if you have relatively short strings or don’t need a lot of slicing</li>
</ul>
<p>In the end, only profiling can really tell which one is better.</p>
<h2 id="pinned-vs-unpinned">Pinned vs unpinned</h2>
<p>Pinned memory means it can’t be moved by the GC. This is useful if we want to move the data directly to foreign code (FFI), without first copying the entire unpinned data to a pinned memory region at the FFI boundary. But it also means that we get memory fragmentation, exactly because the GC cannot move stuff around. If you have lots of small pieces of data with pinned memory, that can severely fragment the heap.</p>
<p>This and the problems it can cause is explained in more detail in the Well-Typed blog <a href="https://well-typed.com/blog/2020/08/memory-fragmentation/#pinned-data">Understanding Memory Fragmentation</a>.</p>
<p>The problem of memory fragmentation was also one of the things that motivated the original <a href="https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path">Abstract FilePath proposal</a> and later the new <code>OsPath</code> type.</p>
<h2 id="string-types-cheat-sheet">String Types Cheat Sheet</h2>
<p>A few notes on the below table:</p>
<ul>
<li>Unicode aware means whether we have access to text processing functions (e.g. split by Unicode Code Point etc.)</li>
<li>memory overhead means: total words required modulo the payload</li>
<li>the overhead for lazy types is multiplied by the number of chunks</li>
<li>some types are unpinned by default (e.g. <code>ShortByteString</code>) but can manually be constructed as pinned via internal API</li>
</ul>
<p>The memory overhead measurements are best effort and explained in more detail in <a href="https://gist.github.com/hasufell/61ca8ef438cc912e7446bcc7b1f25028">this gist</a>.</p>
<table>
<colgroup>
<col style="width: 11%" />
<col style="width: 24%" />
<col style="width: 7%" />
<col style="width: 19%" />
<col style="width: 15%" />
<col style="width: 4%" />
<col style="width: 4%" />
<col style="width: 7%" />
<col style="width: 5%" />
</colgroup>
<thead>
<tr class="header">
<th>Type</th>
<th>purpose</th>
<th>Unicode aware</th>
<th>internal representation</th>
<th>memory overhead</th>
<th>pinned</th>
<th>slicing</th>
<th>FFI suitable</th>
<th>streaming</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>String</strong></td>
<td>simplicity</td>
<td>yes</td>
<td>List of Unicode Code Points</td>
<td>4 words per char + 1 word</td>
<td>no</td>
<td>--</td>
<td>--</td>
<td>yes</td>
</tr>
<tr class="even">
<td><strong>Text</strong></td>
<td>human readable text</td>
<td>yes</td>
<td>UTF-8 byte array</td>
<td>7 words</td>
<td>no</td>
<td>+</td>
<td>-</td>
<td>no</td>
</tr>
<tr class="odd">
<td><strong>Lazy Text</strong></td>
<td>human readable text</td>
<td>yes</td>
<td>List of chunks of UTF-8 byte arrays</td>
<td>9 words per chunk + 1 word</td>
<td>no</td>
<td>+</td>
<td>-</td>
<td>yes</td>
</tr>
<tr class="even">
<td><strong>ShortText</strong></td>
<td>short human readable texts</td>
<td>yes</td>
<td>UTF-8 byte array</td>
<td>4 words</td>
<td>no</td>
<td>-</td>
<td>-</td>
<td>no</td>
</tr>
<tr class="odd">
<td><strong>ByteString</strong></td>
<td>large byte sequences</td>
<td>no</td>
<td>Word8 byte array (pointer)</td>
<td>10 words</td>
<td>yes</td>
<td>++</td>
<td>++</td>
<td>no</td>
</tr>
<tr class="even">
<td><strong>Lazy ByteString</strong></td>
<td>large byte sequences</td>
<td>no</td>
<td>List of chunks of Word8 byte arrays</td>
<td>12 words per chunk + 1 word</td>
<td>yes</td>
<td>++</td>
<td>++</td>
<td>yes</td>
</tr>
<tr class="odd">
<td><strong>ShortByteString</strong></td>
<td>short byte sequences</td>
<td>no</td>
<td>Word8 byte array</td>
<td>4 words</td>
<td>no</td>
<td>-</td>
<td>+</td>
<td>no</td>
</tr>
<tr class="even">
<td><strong>Bytes</strong></td>
<td>slicable ShortByteString / pinned ByteString</td>
<td>no</td>
<td>Word8 byte array</td>
<td>7 words</td>
<td>both</td>
<td>++</td>
<td>+</td>
<td>no</td>
</tr>
<tr class="odd">
<td><strong>Chunks</strong></td>
<td>Like “Bytes”, but for incremental building</td>
<td>no</td>
<td>List of chunks of Word8 byte arrays</td>
<td>9 words per chunk + 1 word</td>
<td>both</td>
<td>++</td>
<td>+</td>
<td>no</td>
</tr>
<tr class="even">
<td><strong>OsString</strong></td>
<td>interfacing with OS API</td>
<td>no</td>
<td>Word8 or Word16 byte array</td>
<td>4 words</td>
<td>no</td>
<td>-</td>
<td>+</td>
<td>no</td>
</tr>
</tbody>
</table>
<h2 id="construction">Construction</h2>
<p>Now that we know about the different types, we will take a quick look about different ways to construct strings.</p>
<h3 id="string-literals">String literals</h3>
<p>The Haskell report defines <a href="https://www.haskell.org/onlinereport/haskell2010/haskellch2.html#x7-200002.6">Character and String literals</a> as part of the language.</p>
<p>Whenever you write <code>"string"</code> in a Haskell file, the compiler will convert it to/consider it as <code>[Char]</code>. Likewise, <code>'c'</code> will be considered <code>Char</code>.</p>
<h3 id="string-classes">String Classes</h3>
<p>A popular String class is <a href="https://hackage.haskell.org/package/base-4.19.1.0/docs/Data-String.html#t:IsString">IsString</a>, which is defined as:</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true"></a><span class="kw">class</span> <span class="dt">IsString</span> a <span class="kw">where</span></span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true"></a><span class="ot">    fromString ::</span> <span class="dt">String</span> <span class="ot">-&gt;</span> a</span></code></pre></div>
<p>So this allows to convert from <code>String</code> to some other compatible type. Note how the type signature does not allow failure. So the conversion must be total.</p>
<p><code>Text</code>, <code>ByteString</code> and <code>ShortByteString</code> have <code>IsString</code> instances. <code>OsString</code> does not. All these instances have problems though:</p>
<ul>
<li><strong>Text</strong>: as explained earlier, surrogate Unicode Code Points in a String cannot be converted to Text, so you’ll end up with the replacement char <code>U+FFFD</code></li>
<li><strong>ByteString</strong>/<strong>ShortByteString</strong>: these instances <strong>truncate</strong> to 8 bits and are as such arguably broken, see <a href="https://github.com/haskell/bytestring/issues/140#issuecomment-2023002164">Surprising behavior of ByteString literals via IsString</a></li>
</ul>
<p>My personal recommendation is to stay away from this class and use explicit functions like <code>pack</code> instead. However, we could also use QuasiQuoters (more on that later).</p>
<h3 id="overloadedstrings">OverloadedStrings</h3>
<p>This language extensions extends the support for string literals to allow all types that have an <code>IsString</code> instance. This can be convenient when dealing with lots of Text literals. However, it poses two problems:</p>
<ul>
<li>it can make type inference harder (since literals are not merely “String”), so sometimes, having a type annotation is necessary</li>
<li>the caveats explained for the <code>IsString</code> class apply here as well: ByteString doesn’t behave well</li>
</ul>
<p>Example use:</p>
<div class="sourceCode" id="cb20"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true"></a><span class="ot">{-# LANGUAGE OverloadedStrings  #-}</span></span>
<span id="cb20-2"><a href="#cb20-2" aria-hidden="true"></a></span>
<span id="cb20-3"><a href="#cb20-3" aria-hidden="true"></a>myText <span class="ot">=</span> <span class="st">&quot;hello world&quot;</span><span class="ot"> ::</span> <span class="dt">Text</span></span></code></pre></div>
<p>I personally advise against using it.</p>
<h3 id="quasiquoters">QuasiQuoters</h3>
<p>This is yet another method to construct string like types. An alternative to literals. It uses <a href="https://serokell.io/blog/introduction-to-template-haskell">Template Haskell</a>, which are essentially expressions that are run at compile time. This allows us to validate literals much more rigorously and have GHC fail at compile time if we attempt to e.g. construct an invalid UTF-8 sequence as Text.</p>
<p>There are many libraries that support quasiquotation. Lots of them also support interpolation (using Haskell expressions/variables) inside the string) e.g.:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/string-interpolate">string-interpolate</a></li>
<li><a href="https://hackage.haskell.org/package/string-qq">string-qq</a></li>
<li><a href="https://hackage.haskell.org/package/interpolate">interpolate</a></li>
<li><a href="https://hackage.haskell.org/package/PyF">PyF</a></li>
<li><a href="https://hackage.haskell.org/package/raw-strings-qq">raw-strings-qq</a></li>
<li><a href="https://streamly.composewell.com/haddocks/streamly-core-0.2.2/Streamly-Unicode-String.html#v:str">streamly</a></li>
</ul>
<p>I personally prefer <code>string-interpolate</code>. The README gives a <a href="https://gitlab.com/williamyaoh/string-interpolate/blob/master/README.md#comparison-to-other-interpolation-libraries">nice comparison</a> to some other libraries (copy-pasted for convenience):</p>
<table>
<colgroup>
<col style="width: 28%" />
<col style="width: 13%" />
<col style="width: 8%" />
<col style="width: 8%" />
<col style="width: 10%" />
<col style="width: 17%" />
<col style="width: 13%" />
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>string-interpolate</th>
<th>interpolate</th>
<th>formatting</th>
<th>Interpolation</th>
<th>interpolatedstring-perl6</th>
<th>neat-interpolation</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>String/Text support</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>⚠️</td>
<td>✅</td>
<td>⚠️</td>
</tr>
<tr class="even">
<td>ByteString support</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>⚠️</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr class="odd">
<td>Can interpolate arbitrary Show instances</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
</tr>
<tr class="even">
<td>Unicode-aware</td>
<td>✅</td>
<td>❌</td>
<td>⚠️</td>
<td>❌</td>
<td>❌</td>
<td>⚠️</td>
</tr>
<tr class="odd">
<td>Multiline strings</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
<td>✅</td>
</tr>
<tr class="even">
<td>Indentation handling</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr class="odd">
<td>Whitespace/newline chomping</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
<td>❌</td>
</tr>
</tbody>
</table>
<p>An example use case:</p>
<div class="sourceCode" id="cb21"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true"></a><span class="ot">showWelcomeMessage ::</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">Integer</span> <span class="ot">-&gt;</span> <span class="dt">Text</span></span>
<span id="cb21-2"><a href="#cb21-2" aria-hidden="true"></a>showWelcomeMessage username visits <span class="ot">=</span></span>
<span id="cb21-3"><a href="#cb21-3" aria-hidden="true"></a>  [i|Welcome to my website, #{username}! You are visitor #{visits}!|]</span></code></pre></div>
<p>It is important to note that having many quasi-quotations in your source files <strong>can slow down compilation time</strong>. There are also (sometimes) issues with tooling, such as code formatters or <a href="https://haskell-language-server.readthedocs.io/en/stable/">Haskell Language Server</a>.</p>
<p>The <code>OsString</code> type provides its own quasi-quoter <a href="https://hackage.haskell.org/package/os-string-2.0.2.1/docs/System-OsString.html#v:osstr">osstr</a>.</p>
<p>The main advantage, again, is that quasi-quoters can properly fail and do so at compile-time.</p>
<h2 id="conversions">Conversions</h2>
<p>There are many ways to convert from one type to another. I propose here the most safe conversions. For some cases, we will have to provide the encoding, because it cannot be guessed.</p>
<p>The <code>Data.ByteString.Encode</code> module listed further down below is part of the <a href="https://hackage.haskell.org/package/bytestring-encoding-0.1.2.0/docs/Data-ByteString-Encoding.html">bytestring-encoding</a> package, which is not shipped with GHC. There are other similar packages like <a href="https://hackage.haskell.org/package/utf8-string">utf8-string</a>.</p>
<p>Other than that, we only need the packages that provide the types we’re dealing with.</p>
<p>We’re omitting <code>ShortText</code>, because conversions are similar to <code>Text</code>. <code>Bytes</code> can be converted to <code>ByteString</code> or <code>ShortByteString</code> depending on the pinned/unpinned nature and from there we can follow the below strategies.</p>
<h3 id="from-string-to">From String to…</h3>
<p>Let’s write a neat conversion module:</p>
<div class="sourceCode" id="cb22"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true"></a><span class="kw">module</span> <span class="dt">StringConversions</span> <span class="kw">where</span></span>
<span id="cb22-2"><a href="#cb22-2" aria-hidden="true"></a></span>
<span id="cb22-3"><a href="#cb22-3" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString</span> (<span class="dt">ByteString</span>)</span>
<span id="cb22-4"><a href="#cb22-4" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Encoding</span> (<span class="dt">TextEncoding</span>)</span>
<span id="cb22-5"><a href="#cb22-5" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Short</span> (<span class="dt">ShortByteString</span>)</span>
<span id="cb22-6"><a href="#cb22-6" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb22-7"><a href="#cb22-7" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString</span></span>
<span id="cb22-8"><a href="#cb22-8" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString.Encoding</span> (<span class="dt">EncodingException</span>)</span>
<span id="cb22-9"><a href="#cb22-9" aria-hidden="true"></a></span>
<span id="cb22-10"><a href="#cb22-10" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Encoding</span> <span class="kw">as</span> <span class="dt">BE</span></span>
<span id="cb22-11"><a href="#cb22-11" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Short</span> <span class="kw">as</span> <span class="dt">SBS</span></span>
<span id="cb22-12"><a href="#cb22-12" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Text</span> <span class="kw">as</span> <span class="dt">T</span></span>
<span id="cb22-13"><a href="#cb22-13" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsString</span> <span class="kw">as</span> <span class="dt">OS</span></span>
<span id="cb22-14"><a href="#cb22-14" aria-hidden="true"></a></span>
<span id="cb22-15"><a href="#cb22-15" aria-hidden="true"></a><span class="ot">toString ::</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">String</span></span>
<span id="cb22-16"><a href="#cb22-16" aria-hidden="true"></a>toString <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb22-17"><a href="#cb22-17" aria-hidden="true"></a></span>
<span id="cb22-18"><a href="#cb22-18" aria-hidden="true"></a><span class="ot">toText ::</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">Text</span></span>
<span id="cb22-19"><a href="#cb22-19" aria-hidden="true"></a>toText <span class="ot">=</span> T.pack</span>
<span id="cb22-20"><a href="#cb22-20" aria-hidden="true"></a></span>
<span id="cb22-21"><a href="#cb22-21" aria-hidden="true"></a><span class="ot">toByteString ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">ByteString</span></span>
<span id="cb22-22"><a href="#cb22-22" aria-hidden="true"></a>toByteString encoding <span class="ot">=</span> BE.encode encoding <span class="op">.</span> T.pack</span>
<span id="cb22-23"><a href="#cb22-23" aria-hidden="true"></a></span>
<span id="cb22-24"><a href="#cb22-24" aria-hidden="true"></a><span class="ot">toShortByteString ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">ShortByteString</span></span>
<span id="cb22-25"><a href="#cb22-25" aria-hidden="true"></a>toShortByteString encoding <span class="ot">=</span> SBS.toShort <span class="op">.</span> BE.encode encoding <span class="op">.</span> T.pack</span>
<span id="cb22-26"><a href="#cb22-26" aria-hidden="true"></a></span>
<span id="cb22-27"><a href="#cb22-27" aria-hidden="true"></a><span class="ot">toOsString ::</span> (<span class="dt">TextEncoding</span>, <span class="dt">TextEncoding</span>) <span class="ot">-&gt;</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">EncodingException</span> <span class="dt">OsString</span></span>
<span id="cb22-28"><a href="#cb22-28" aria-hidden="true"></a>toOsString (unixEncoding, windowsEncoding) <span class="ot">=</span> OS.encodeWith unixEncoding windowsEncoding</span></code></pre></div>
<p>For converting to <code>ByteString</code> and <code>ShortByteString</code>, we have to explicitly specify an encoding for the resulting byte sequence. For <code>OsString</code> we have to provide encodings per platform, since this type is platform agnostic.</p>
<p>The caveat wrt. Text’s <code>pack</code> not dealing well with surrogates applies.</p>
<h3 id="from-text-to">From Text to…</h3>
<div class="sourceCode" id="cb23"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true"></a><span class="kw">module</span> <span class="dt">TextConversions</span> <span class="kw">where</span></span>
<span id="cb23-2"><a href="#cb23-2" aria-hidden="true"></a></span>
<span id="cb23-3"><a href="#cb23-3" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString</span> (<span class="dt">ByteString</span>)</span>
<span id="cb23-4"><a href="#cb23-4" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Encoding</span> (<span class="dt">TextEncoding</span>)</span>
<span id="cb23-5"><a href="#cb23-5" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Short</span> (<span class="dt">ShortByteString</span>)</span>
<span id="cb23-6"><a href="#cb23-6" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb23-7"><a href="#cb23-7" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString</span></span>
<span id="cb23-8"><a href="#cb23-8" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString.Encoding</span> (<span class="dt">EncodingException</span>)</span>
<span id="cb23-9"><a href="#cb23-9" aria-hidden="true"></a></span>
<span id="cb23-10"><a href="#cb23-10" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Encoding</span> <span class="kw">as</span> <span class="dt">BE</span></span>
<span id="cb23-11"><a href="#cb23-11" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Short</span> <span class="kw">as</span> <span class="dt">SBS</span></span>
<span id="cb23-12"><a href="#cb23-12" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Text</span> <span class="kw">as</span> <span class="dt">T</span></span>
<span id="cb23-13"><a href="#cb23-13" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsString</span> <span class="kw">as</span> <span class="dt">OS</span></span>
<span id="cb23-14"><a href="#cb23-14" aria-hidden="true"></a></span>
<span id="cb23-15"><a href="#cb23-15" aria-hidden="true"></a><span class="ot">toString ::</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">String</span></span>
<span id="cb23-16"><a href="#cb23-16" aria-hidden="true"></a>toString <span class="ot">=</span> T.unpack</span>
<span id="cb23-17"><a href="#cb23-17" aria-hidden="true"></a></span>
<span id="cb23-18"><a href="#cb23-18" aria-hidden="true"></a><span class="ot">toText ::</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">Text</span></span>
<span id="cb23-19"><a href="#cb23-19" aria-hidden="true"></a>toText <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb23-20"><a href="#cb23-20" aria-hidden="true"></a></span>
<span id="cb23-21"><a href="#cb23-21" aria-hidden="true"></a><span class="ot">toByteString ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">ByteString</span></span>
<span id="cb23-22"><a href="#cb23-22" aria-hidden="true"></a>toByteString encoding <span class="ot">=</span> BE.encode encoding</span>
<span id="cb23-23"><a href="#cb23-23" aria-hidden="true"></a></span>
<span id="cb23-24"><a href="#cb23-24" aria-hidden="true"></a><span class="ot">toShortByteString ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">ShortByteString</span></span>
<span id="cb23-25"><a href="#cb23-25" aria-hidden="true"></a>toShortByteString encoding <span class="ot">=</span> SBS.toShort <span class="op">.</span> BE.encode encoding</span>
<span id="cb23-26"><a href="#cb23-26" aria-hidden="true"></a></span>
<span id="cb23-27"><a href="#cb23-27" aria-hidden="true"></a><span class="ot">toOsString ::</span> (<span class="dt">TextEncoding</span>, <span class="dt">TextEncoding</span>) <span class="ot">-&gt;</span> <span class="dt">Text</span> <span class="ot">-&gt;</span> <span class="dt">Either</span> <span class="dt">EncodingException</span> <span class="dt">OsString</span></span>
<span id="cb23-28"><a href="#cb23-28" aria-hidden="true"></a>toOsString (unixEncoding, windowsEncoding) <span class="ot">=</span> OS.encodeWith unixEncoding windowsEncoding <span class="op">.</span> T.unpack</span></code></pre></div>
<p>When converting from <code>Text</code>, we can essentially reuse all the API that deals with just <code>String</code> and vice versa.</p>
<h3 id="from-bytestring-to">From ByteString to…</h3>
<div class="sourceCode" id="cb24"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true"></a><span class="kw">module</span> <span class="dt">ByteStringConversions</span> <span class="kw">where</span></span>
<span id="cb24-2"><a href="#cb24-2" aria-hidden="true"></a></span>
<span id="cb24-3"><a href="#cb24-3" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString</span> (<span class="dt">ByteString</span>)</span>
<span id="cb24-4"><a href="#cb24-4" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Encoding</span> (<span class="dt">TextEncoding</span>)</span>
<span id="cb24-5"><a href="#cb24-5" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Short</span> (<span class="dt">ShortByteString</span>)</span>
<span id="cb24-6"><a href="#cb24-6" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb24-7"><a href="#cb24-7" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString</span></span>
<span id="cb24-8"><a href="#cb24-8" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString.Encoding</span> (<span class="dt">EncodingException</span>)</span>
<span id="cb24-9"><a href="#cb24-9" aria-hidden="true"></a></span>
<span id="cb24-10"><a href="#cb24-10" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Encoding</span> <span class="kw">as</span> <span class="dt">BE</span></span>
<span id="cb24-11"><a href="#cb24-11" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Short</span> <span class="kw">as</span> <span class="dt">SBS</span></span>
<span id="cb24-12"><a href="#cb24-12" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Text</span> <span class="kw">as</span> <span class="dt">T</span></span>
<span id="cb24-13"><a href="#cb24-13" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsString</span> <span class="kw">as</span> <span class="dt">OS</span></span>
<span id="cb24-14"><a href="#cb24-14" aria-hidden="true"></a></span>
<span id="cb24-15"><a href="#cb24-15" aria-hidden="true"></a><span class="ot">toString ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">ByteString</span> <span class="ot">-&gt;</span> <span class="dt">String</span></span>
<span id="cb24-16"><a href="#cb24-16" aria-hidden="true"></a>toString encoding <span class="ot">=</span> T.unpack <span class="op">.</span> BE.decode encoding</span>
<span id="cb24-17"><a href="#cb24-17" aria-hidden="true"></a></span>
<span id="cb24-18"><a href="#cb24-18" aria-hidden="true"></a><span class="ot">toText ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">ByteString</span> <span class="ot">-&gt;</span> <span class="dt">Text</span></span>
<span id="cb24-19"><a href="#cb24-19" aria-hidden="true"></a>toText encoding <span class="ot">=</span> BE.decode encoding</span>
<span id="cb24-20"><a href="#cb24-20" aria-hidden="true"></a></span>
<span id="cb24-21"><a href="#cb24-21" aria-hidden="true"></a><span class="ot">toByteString ::</span> <span class="dt">ByteString</span> <span class="ot">-&gt;</span> <span class="dt">ByteString</span></span>
<span id="cb24-22"><a href="#cb24-22" aria-hidden="true"></a>toByteString <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb24-23"><a href="#cb24-23" aria-hidden="true"></a></span>
<span id="cb24-24"><a href="#cb24-24" aria-hidden="true"></a><span class="ot">toShortByteString ::</span> <span class="dt">ByteString</span> <span class="ot">-&gt;</span> <span class="dt">ShortByteString</span></span>
<span id="cb24-25"><a href="#cb24-25" aria-hidden="true"></a>toShortByteString <span class="ot">=</span> SBS.toShort</span>
<span id="cb24-26"><a href="#cb24-26" aria-hidden="true"></a></span>
<span id="cb24-27"><a href="#cb24-27" aria-hidden="true"></a><span class="co">-- | This is hard to write correctly. It depends on where the @ByteString@</span></span>
<span id="cb24-28"><a href="#cb24-28" aria-hidden="true"></a><span class="co">-- comes from. It may not be possible to interpret it on both platforms.</span></span>
<span id="cb24-29"><a href="#cb24-29" aria-hidden="true"></a><span class="co">-- @OsString@ is meant to interface with operating system API, not to manually</span></span>
<span id="cb24-30"><a href="#cb24-30" aria-hidden="true"></a><span class="co">-- construct arbitrary strings. Use the @osstr@ quasi quoter if you need</span></span>
<span id="cb24-31"><a href="#cb24-31" aria-hidden="true"></a><span class="co">-- literals. Or look at the internals in &#39;System.OsString.Internal.Types&#39;.</span></span>
<span id="cb24-32"><a href="#cb24-32" aria-hidden="true"></a><span class="ot">toOsString ::</span> <span class="dt">ByteString</span> <span class="ot">-&gt;</span> <span class="dt">OsString</span></span>
<span id="cb24-33"><a href="#cb24-33" aria-hidden="true"></a>toOsString <span class="ot">=</span> <span class="fu">undefined</span></span></code></pre></div>
<p>For converting to <code>String</code> and <code>Text</code>, we have to provide an encoding for the ByteString in order to decode it.</p>
<p>Converting from a byte sequence of unknown origin to <code>OsString</code> is hard. The way this usually happens is at the FFI boundaries in <code>Win32</code> and <code>unix</code> package. The question is what does the given byte sequence represent… where does it come from, what is its encoding, if any? If it comes from operating system API, we can just wrap it into our types, see <a href="https://hackage.haskell.org/package/os-string-2.0.2.1/docs/System-OsString-Internal-Types.html">System.OsString.Internal.Types</a>. Otherwise, we may need to decode the bytes first and then pick a target encoding.</p>
<h3 id="from-shortbytestring-to">From ShortByteString to…</h3>
<div class="sourceCode" id="cb25"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true"></a><span class="kw">module</span> <span class="dt">ByteStringConversions</span> <span class="kw">where</span></span>
<span id="cb25-2"><a href="#cb25-2" aria-hidden="true"></a></span>
<span id="cb25-3"><a href="#cb25-3" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString</span> (<span class="dt">ByteString</span>)</span>
<span id="cb25-4"><a href="#cb25-4" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Encoding</span> (<span class="dt">TextEncoding</span>)</span>
<span id="cb25-5"><a href="#cb25-5" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Short</span> (<span class="dt">ShortByteString</span>)</span>
<span id="cb25-6"><a href="#cb25-6" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb25-7"><a href="#cb25-7" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString</span></span>
<span id="cb25-8"><a href="#cb25-8" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString.Encoding</span> (<span class="dt">EncodingException</span>)</span>
<span id="cb25-9"><a href="#cb25-9" aria-hidden="true"></a></span>
<span id="cb25-10"><a href="#cb25-10" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Encoding</span> <span class="kw">as</span> <span class="dt">BE</span></span>
<span id="cb25-11"><a href="#cb25-11" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Short</span> <span class="kw">as</span> <span class="dt">SBS</span></span>
<span id="cb25-12"><a href="#cb25-12" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Text</span> <span class="kw">as</span> <span class="dt">T</span></span>
<span id="cb25-13"><a href="#cb25-13" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsString</span> <span class="kw">as</span> <span class="dt">OS</span></span>
<span id="cb25-14"><a href="#cb25-14" aria-hidden="true"></a></span>
<span id="cb25-15"><a href="#cb25-15" aria-hidden="true"></a><span class="ot">toString ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">ShortByteString</span> <span class="ot">-&gt;</span> <span class="dt">String</span></span>
<span id="cb25-16"><a href="#cb25-16" aria-hidden="true"></a>toString encoding <span class="ot">=</span> T.unpack <span class="op">.</span> BE.decode encoding <span class="op">.</span> SBS.fromShort</span>
<span id="cb25-17"><a href="#cb25-17" aria-hidden="true"></a></span>
<span id="cb25-18"><a href="#cb25-18" aria-hidden="true"></a><span class="ot">toText ::</span> <span class="dt">TextEncoding</span> <span class="ot">-&gt;</span> <span class="dt">ShortByteString</span> <span class="ot">-&gt;</span> <span class="dt">Text</span></span>
<span id="cb25-19"><a href="#cb25-19" aria-hidden="true"></a>toText encoding <span class="ot">=</span> BE.decode encoding <span class="op">.</span> SBS.fromShort</span>
<span id="cb25-20"><a href="#cb25-20" aria-hidden="true"></a></span>
<span id="cb25-21"><a href="#cb25-21" aria-hidden="true"></a><span class="ot">toByteString ::</span> <span class="dt">ShortByteString</span> <span class="ot">-&gt;</span> <span class="dt">ByteString</span></span>
<span id="cb25-22"><a href="#cb25-22" aria-hidden="true"></a>toByteString <span class="ot">=</span> SBS.fromShort</span>
<span id="cb25-23"><a href="#cb25-23" aria-hidden="true"></a></span>
<span id="cb25-24"><a href="#cb25-24" aria-hidden="true"></a><span class="ot">toShortByteString ::</span> <span class="dt">ShortByteString</span> <span class="ot">-&gt;</span> <span class="dt">ShortByteString</span></span>
<span id="cb25-25"><a href="#cb25-25" aria-hidden="true"></a>toShortByteString <span class="ot">=</span> <span class="fu">id</span></span>
<span id="cb25-26"><a href="#cb25-26" aria-hidden="true"></a></span>
<span id="cb25-27"><a href="#cb25-27" aria-hidden="true"></a><span class="co">-- | This is hard to write correctly. It depends on where the @ShortByteString@</span></span>
<span id="cb25-28"><a href="#cb25-28" aria-hidden="true"></a><span class="co">-- comes from. It may not be possible to interpret it on both platforms.</span></span>
<span id="cb25-29"><a href="#cb25-29" aria-hidden="true"></a><span class="co">-- @OsString@ is meant to interface with operating system API, not to manually</span></span>
<span id="cb25-30"><a href="#cb25-30" aria-hidden="true"></a><span class="co">-- construct arbitrary strings. Use the @osstr@ quasi quoter if you need</span></span>
<span id="cb25-31"><a href="#cb25-31" aria-hidden="true"></a><span class="co">-- literals. Or look at the internals in &#39;System.OsString.Internal.Types&#39;.</span></span>
<span id="cb25-32"><a href="#cb25-32" aria-hidden="true"></a><span class="ot">toOsString ::</span> <span class="dt">ShortByteString</span> <span class="ot">-&gt;</span> <span class="dt">OsString</span></span>
<span id="cb25-33"><a href="#cb25-33" aria-hidden="true"></a>toOsString <span class="ot">=</span> <span class="fu">undefined</span></span></code></pre></div>
<p>The same caveats as for ByteString apply.</p>
<h3 id="from-osstring-to">From OsString to…</h3>
<div class="sourceCode" id="cb26"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true"></a><span class="kw">module</span> <span class="dt">OsStringConversions</span> <span class="kw">where</span></span>
<span id="cb26-2"><a href="#cb26-2" aria-hidden="true"></a></span>
<span id="cb26-3"><a href="#cb26-3" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Control.Monad.Catch</span> (<span class="dt">MonadThrow</span>)</span>
<span id="cb26-4"><a href="#cb26-4" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString</span> (<span class="dt">ByteString</span>)</span>
<span id="cb26-5"><a href="#cb26-5" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Encoding</span> (<span class="dt">TextEncoding</span>)</span>
<span id="cb26-6"><a href="#cb26-6" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.ByteString.Short</span> (<span class="dt">ShortByteString</span>)</span>
<span id="cb26-7"><a href="#cb26-7" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Data.Text</span> (<span class="dt">Text</span>)</span>
<span id="cb26-8"><a href="#cb26-8" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString</span></span>
<span id="cb26-9"><a href="#cb26-9" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString.Encoding</span> (<span class="dt">EncodingException</span>)</span>
<span id="cb26-10"><a href="#cb26-10" aria-hidden="true"></a></span>
<span id="cb26-11"><a href="#cb26-11" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Encoding</span> <span class="kw">as</span> <span class="dt">BE</span></span>
<span id="cb26-12"><a href="#cb26-12" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.ByteString.Short</span> <span class="kw">as</span> <span class="dt">SBS</span></span>
<span id="cb26-13"><a href="#cb26-13" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Text</span> <span class="kw">as</span> <span class="dt">T</span></span>
<span id="cb26-14"><a href="#cb26-14" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsString</span> <span class="kw">as</span> <span class="dt">OS</span></span>
<span id="cb26-15"><a href="#cb26-15" aria-hidden="true"></a></span>
<span id="cb26-16"><a href="#cb26-16" aria-hidden="true"></a><span class="ot">toString ::</span> <span class="dt">MonadThrow</span> m <span class="ot">=&gt;</span> <span class="dt">OsString</span> <span class="ot">-&gt;</span> m <span class="dt">String</span></span>
<span id="cb26-17"><a href="#cb26-17" aria-hidden="true"></a>toString <span class="ot">=</span> OS.decodeUtf</span>
<span id="cb26-18"><a href="#cb26-18" aria-hidden="true"></a></span>
<span id="cb26-19"><a href="#cb26-19" aria-hidden="true"></a><span class="ot">toText ::</span> <span class="dt">MonadThrow</span> m <span class="ot">=&gt;</span> <span class="dt">OsString</span> <span class="ot">-&gt;</span> m <span class="dt">Text</span></span>
<span id="cb26-20"><a href="#cb26-20" aria-hidden="true"></a>toText <span class="ot">=</span> <span class="fu">fmap</span> T.pack <span class="op">.</span> OS.decodeUtf</span>
<span id="cb26-21"><a href="#cb26-21" aria-hidden="true"></a></span>
<span id="cb26-22"><a href="#cb26-22" aria-hidden="true"></a><span class="co">-- | It depends whether we want the original bytes passed unchanged</span></span>
<span id="cb26-23"><a href="#cb26-23" aria-hidden="true"></a><span class="co">-- and platform specific or whether we want to convert to a unified</span></span>
<span id="cb26-24"><a href="#cb26-24" aria-hidden="true"></a><span class="co">-- representation that is the same on both platforms, but in ByteString</span></span>
<span id="cb26-25"><a href="#cb26-25" aria-hidden="true"></a><span class="co">-- format.</span></span>
<span id="cb26-26"><a href="#cb26-26" aria-hidden="true"></a><span class="ot">toByteString ::</span> <span class="dt">OsString</span> <span class="ot">-&gt;</span> <span class="dt">ByteString</span></span>
<span id="cb26-27"><a href="#cb26-27" aria-hidden="true"></a>toByteString <span class="ot">=</span> <span class="fu">undefined</span></span>
<span id="cb26-28"><a href="#cb26-28" aria-hidden="true"></a></span>
<span id="cb26-29"><a href="#cb26-29" aria-hidden="true"></a><span class="co">-- | Same as &#39;toByteString&#39;.</span></span>
<span id="cb26-30"><a href="#cb26-30" aria-hidden="true"></a><span class="ot">toShortByteString ::</span> <span class="dt">OsString</span> <span class="ot">-&gt;</span> <span class="dt">ShortByteString</span></span>
<span id="cb26-31"><a href="#cb26-31" aria-hidden="true"></a>toShortByteString <span class="ot">=</span> <span class="fu">undefined</span></span>
<span id="cb26-32"><a href="#cb26-32" aria-hidden="true"></a></span>
<span id="cb26-33"><a href="#cb26-33" aria-hidden="true"></a><span class="ot">toOsString ::</span> <span class="dt">OsString</span> <span class="ot">-&gt;</span> <span class="dt">OsString</span></span>
<span id="cb26-34"><a href="#cb26-34" aria-hidden="true"></a>toOsString <span class="ot">=</span> <span class="fu">id</span></span></code></pre></div>
<p>OsString always comes with 3 families of decoding and encoding functions:</p>
<ul>
<li><code>encodeUtf</code>/<code>decodeUtf</code>: assumes UTF-8 on Unix and UTF-16 LE on Windows
<ul>
<li>we are using this in the code above for simplicity</li>
</ul></li>
<li><code>encodeWith</code>/<code>decodeWith</code>: here we have to pass the encoding for both platforms explicitly</li>
<li><code>encodeFS</code>/<code>decodeFS</code>: this mimics the behavior of the base library, using PEP-383 style encoding on Unix and permissive UTF-16 on Windows</li>
</ul>
<h3 id="to-json">To JSON</h3>
<p>A lot of times we want to send our strings over the wire, possibly via JSON. We will examine this via the popular <a href="https://hackage.haskell.org/package/aeson">aeson</a> library.</p>
<p>Both <code>Text</code> and <code>String</code> already have <code>ToJSON</code> <a href="https://hackage.haskell.org/package/aeson-2.2.1.0/docs/Data-Aeson.html#t:ToJSON">instances</a>. These are easy, because they are Unicode and <a href="https://www.rfc-editor.org/rfc/rfc8259#section-8.1">JSON demands UTF-8</a>.</p>
<p>For <code>ByteString</code>, <code>ShortByteString</code> and <code>OsString</code> this gets a bit more complicated. It depends on the exact use case. What is the byte sequence used for on the machine receiving the JSON? Also see the discussion <a href="https://github.com/haskell/aeson/issues/187">Add saner ByteString instances</a> on the aeson issue tracker.</p>
<p>From my perspective, there are 3 possibilities:</p>
<ol type="1">
<li>convert to <code>String</code> (e.g. by assuming UTF-8 or UTF-16), use the existing ToJSON instance and hope the receiver knows how to interpret the data</li>
<li>if you’re dealing with binary data, you can convert to e.g. base64 String or Text and then again use the existing instances (there’s the <a href="https://hackage.haskell.org/package/base64-bytestring-type-1.0.1/docs/Data-ByteString-Base64-Type.html">base64-bytestring-type</a> library that does this via a newtype)</li>
<li>convert the byte sequence to <code>[Word8]</code>, which has a valid instance as well</li>
</ol>
<p>For the case of <code>OsString</code>, keep in mind that the raw bytes depend on the current platform (<code>char[]</code> array on Unix and <code>wchar_t*</code> on Windows). So you may have to attach more information if you choose methods 2 and 3 (e.g. encoding of the byte sequence and platform). And you need a strategy to deal with e.g. a Windows machine sending binary data to a Unix machine. As such, I recommend using <a href="https://hackage.haskell.org/package/os-string-2.0.2.1/docs/System-OsString.html#g:3"><code>decodeUtf</code></a> to get a String. The target machine can then use <a href="https://hackage.haskell.org/package/os-string-2.0.2.1/docs/System-OsString.html#v:encodeUtf"><code>encodeUtf</code></a> to get back an OsString.</p>
<h2 id="a-word-on-lazy-io">A word on lazy IO</h2>
<p>Some of the named packages expose API for reading and writing files via their lazy variants:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/text-2.1.1/docs/Data-Text-Lazy-IO.html#v:readFile">Data.Text.Lazy.IO.readfile</a></li>
<li><a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString-Lazy.html#v:readFile">Data.ByteString.Lazy.readFile</a></li>
</ul>
<p>Lazy IO is a hack to use incremental reading/processing without the use of a proper streaming library. The <a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString-Lazy.html#g:25">bytestring documentation</a> warns about it:</p>
<blockquote>
<ul>
<li>The program reads a file and writes the same file. This means that the file may be locked because the handler has not been released when writeFile is executed.</li>
<li>The program reads thousands of files, but due to lazy evaluation, the OS’s file descriptor limit is reached before the handlers can be released.</li>
</ul>
</blockquote>
<p>Lazy IO makes it hard to reason about resources, order of execution etc. It is better to use a proper streaming library.</p>
<h2 id="streaming">Streaming</h2>
<p>Streaming can not only solve the lazy IO problem, but may also solve some of the inefficiency of the <code>[Char]</code> type and can be more perfomant than lazy Text/ByteString, while keeping a similarly simple API.</p>
<p>There are many popular streaming libraries. A few of them are:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/conduit">conduit</a></li>
<li><a href="https://hackage.haskell.org/package/streaming">streaming</a></li>
<li><a href="https://hackage.haskell.org/package/streamly">streamly</a></li>
<li><a href="https://hackage.haskell.org/package/pipes">pipes</a></li>
</ul>
<h3 id="via-streamly">Via Streamly</h3>
<p>A couple of years ago I wrote the blog post <a href="https://hasufell.github.io/posts/2021-10-22-conduit-to-streamly.html">From conduit to streamly</a>, which gives an introduction into both streamly and conduit. The streamly API has diverged quite a bit since then, with multiple major versions. So I won’t go into much detail about it.</p>
<p>However, streamly is one notable example which provides an alternative to the <code>[Char]</code> type in <a href="https://hackage.haskell.org/package/streamly-core-0.2.2/docs/Streamly-Unicode-Stream.html">Streamly.Unicode.Stream</a>:</p>
<div class="sourceCode" id="cb27"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true"></a><span class="ot">decodeUtf8 ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">Stream</span> m <span class="dt">Word8</span> <span class="ot">-&gt;</span> <span class="dt">Stream</span> m <span class="dt">Char</span></span>
<span id="cb27-2"><a href="#cb27-2" aria-hidden="true"></a><span class="ot">encodeUtf8 ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">Stream</span> m <span class="dt">Char</span> <span class="ot">-&gt;</span> <span class="dt">Stream</span> m <span class="dt">Word8</span></span></code></pre></div>
<p>A very simple program to print the last Unicode char of a file via streamly is:</p>
<div class="sourceCode" id="cb28"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.Environment</span> (getArgs)</span>
<span id="cb28-2"><a href="#cb28-2" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Streamly.Data.Stream</span> (<span class="dt">Stream</span>)</span>
<span id="cb28-3"><a href="#cb28-3" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">Streamly.Data.Fold</span> (<span class="dt">Fold</span>)</span>
<span id="cb28-4"><a href="#cb28-4" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Streamly.Data.Fold</span> <span class="kw">as</span> <span class="dt">Fold</span></span>
<span id="cb28-5"><a href="#cb28-5" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Streamly.FileSystem.File</span> <span class="kw">as</span> <span class="dt">File</span></span>
<span id="cb28-6"><a href="#cb28-6" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Streamly.Unicode.Stream</span> <span class="kw">as</span> <span class="dt">Unicode</span></span>
<span id="cb28-7"><a href="#cb28-7" aria-hidden="true"></a></span>
<span id="cb28-8"><a href="#cb28-8" aria-hidden="true"></a><span class="ot">main ::</span> <span class="dt">IO</span> ()</span>
<span id="cb28-9"><a href="#cb28-9" aria-hidden="true"></a>main <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb28-10"><a href="#cb28-10" aria-hidden="true"></a>  (file<span class="op">:</span>_) <span class="ot">&lt;-</span> getArgs</span>
<span id="cb28-11"><a href="#cb28-11" aria-hidden="true"></a>  c <span class="ot">&lt;-</span> getLastCharFromFile file</span>
<span id="cb28-12"><a href="#cb28-12" aria-hidden="true"></a>  <span class="fu">print</span> c</span>
<span id="cb28-13"><a href="#cb28-13" aria-hidden="true"></a></span>
<span id="cb28-14"><a href="#cb28-14" aria-hidden="true"></a><span class="ot">getLastCharFromFile ::</span> <span class="dt">FilePath</span> <span class="ot">-&gt;</span> <span class="dt">IO</span> (<span class="dt">Maybe</span> <span class="dt">Char</span>)</span>
<span id="cb28-15"><a href="#cb28-15" aria-hidden="true"></a>getLastCharFromFile file <span class="ot">=</span> stream <span class="ot">`Fold.drive`</span> fold</span>
<span id="cb28-16"><a href="#cb28-16" aria-hidden="true"></a> <span class="kw">where</span></span>
<span id="cb28-17"><a href="#cb28-17" aria-hidden="true"></a><span class="ot">  stream ::</span> <span class="dt">Stream</span> <span class="dt">IO</span> <span class="dt">Char</span></span>
<span id="cb28-18"><a href="#cb28-18" aria-hidden="true"></a>  stream <span class="ot">=</span> Unicode.decodeUtf8Chunks <span class="op">$</span> File.readChunks file</span>
<span id="cb28-19"><a href="#cb28-19" aria-hidden="true"></a></span>
<span id="cb28-20"><a href="#cb28-20" aria-hidden="true"></a><span class="ot">  fold ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">Fold</span> m a (<span class="dt">Maybe</span> a)</span>
<span id="cb28-21"><a href="#cb28-21" aria-hidden="true"></a>  fold <span class="ot">=</span> Fold.latest</span></code></pre></div>
<p>To compile this program you need the <code>streamly-core</code> package. As we can see here we can create streams of Unicode Chars easily while reading from a file… without lazy IO and without the need for the lazy Text type.</p>
<p>If you want to compare the performance of string vs text vs streamly, you can check out the code here in my <a href="https://github.com/hasufell/streamly-string">example repository</a>. My results on a 189MB file are:</p>
<ul>
<li>string: 1,152s</li>
<li>lazy text: 0,654s</li>
<li>streamly: 0,222s</li>
</ul>
<h2 id="a-note-on-filepaths">A note on FilePaths</h2>
<p>Just a quick reminder:</p>
<ul>
<li><code>String</code> for filepaths is very wrong</li>
<li><code>Text</code> for filepaths is wrong</li>
<li><code>ByteString</code> for filepaths is questionable</li>
<li><code>OsPath</code> for filepaths is good</li>
</ul>
<p>For more details, read up on:</p>
<ul>
<li><a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">Fixing Haskell filepaths, by Julian Ospald</a></li>
<li><a href="https://hackage.haskell.org/package/filepath-1.5.2.0/docs/System-OsPath.html">System.OsPath haddocks</a></li>
</ul>
<h2 id="reflection">Reflection</h2>
<h3 id="what-we-should-know">What we should know</h3>
<p>Almost at the end of the post, we should now have some insights into Unicode and understand:</p>
<ul>
<li>what a character encoding is (Unicode Code Point)</li>
<li>what a text encoding is (UTF-8, UTF-16, UTF-32)</li>
<li>how the different Unicode Transformation Formats work
<ul>
<li>and their trade offs (word boundaries, searching, spaces)</li>
</ul></li>
<li>the problems with Code Points and Surrogates
<ul>
<li>and how this affects the <code>Char</code> type, <code>Text</code> and the <code>IsString</code> instance</li>
</ul></li>
<li>that grapheme clusters are the closest definition of “visible symbol”
<ul>
<li>and that they can consist of multiple code points</li>
</ul></li>
<li>that only UTF-8 is ASCII compatible</li>
</ul>
<p>We understand the weird numbers that the <code>Show</code> instance of <code>Char</code>/<code>String</code> sometimes returns.</p>
<p>We have seen a summary of the different string types:</p>
<ul>
<li>Text/ShortText for Unicode</li>
<li>ByteString/ShortByteString for binary data</li>
<li>The very flexible Bytes type</li>
<li>OsString for operating systems API</li>
<li>String for the bin</li>
</ul>
<p>We know how to construct strings safely, can utilize QuasiQuoters to do compile-time validation and know how to convert between different types and how to deal with JSON.</p>
<p>We know the dangers of lazy IO and how to utilize streaming libraries instead.</p>
<h3 id="too-many-strings">Too many Strings</h3>
<p>After all these topics, I want to address the opinion that gets thrown around on the internet a lot: “Haskell has too many String types”, e.g. on <a href="https://news.ycombinator.com/item?id=14567755">Hacker News</a>.</p>
<p>If we take another look at the <a href="#string-types-cheat-sheet">String Types Cheat Sheet</a>, we don’t really see any type that could be replaced by another. They all have different properties and trade-offs. <code>ByteString</code> vs <code>ShortByteString</code> may be a bit less intuitive, but e.g. <code>Text</code> is clearly different. <code>OsPath</code> is a specialized type that exists in Rust too.</p>
<p>Maybe people dislike the Lazy variants and prefer proper streaming libraries, which is a fair point. But even if the community decides to shift, now you have another type (it’s just a streaming type), have to learn streaming library API and decide which of those 5+ libraries to use. So while we could technically do away with them, they’re a useful low-entry barrier alternative and are still widely used.</p>
<p>In the end, once all these properties are well understood, I find it hard to make an argument for less types. However, it is clear that not everyone thinks so:</p>
<ul>
<li><a href="https://www.snoyman.com/blog/2021/03/haskell-base-proposal/">Haskell base proposal: unifying vector-like types</a></li>
<li><a href="https://discourse.haskell.org/t/base-proposal-around-vector-like-types/2112">Discourse thread on vector proposal</a></li>
</ul>
<p>I am still unable to see the bigger picture, other than more unification of <em>internal representations</em>, but less so of public APIs.</p>
<p>E.g. if we compare the following 3 types, we see a pattern:</p>
<div class="sourceCode" id="cb29"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Text</span> <span class="ot">=</span> <span class="dt">Text</span></span>
<span id="cb29-2"><a href="#cb29-2" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">A.Array</span> <span class="co">-- ^ bytearray encoded as UTF-8</span></span>
<span id="cb29-3"><a href="#cb29-3" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span>     <span class="co">-- ^ offset in bytes (not in Char!), pointing to a start of UTF-8 sequence</span></span>
<span id="cb29-4"><a href="#cb29-4" aria-hidden="true"></a>    <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span>     <span class="co">-- ^ length in bytes (not in Char!), pointing to an end of UTF-8 sequence</span></span>
<span id="cb29-5"><a href="#cb29-5" aria-hidden="true"></a></span>
<span id="cb29-6"><a href="#cb29-6" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Vector</span> a <span class="ot">=</span> <span class="dt">Vector</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span></span>
<span id="cb29-7"><a href="#cb29-7" aria-hidden="true"></a>                       <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span></span>
<span id="cb29-8"><a href="#cb29-8" aria-hidden="true"></a>                       <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span>(<span class="dt">Array</span> a)</span>
<span id="cb29-9"><a href="#cb29-9" aria-hidden="true"></a></span>
<span id="cb29-10"><a href="#cb29-10" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">Bytes</span> <span class="ot">=</span> <span class="dt">Bytes</span></span>
<span id="cb29-11"><a href="#cb29-11" aria-hidden="true"></a>  {<span class="ot"> array ::</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">ByteArray</span></span>
<span id="cb29-12"><a href="#cb29-12" aria-hidden="true"></a>  ,<span class="ot"> offset ::</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span></span>
<span id="cb29-13"><a href="#cb29-13" aria-hidden="true"></a>  ,<span class="ot"> length ::</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Int</span></span>
<span id="cb29-14"><a href="#cb29-14" aria-hidden="true"></a>  }</span></code></pre></div>
<p>A unification of internal representations would e.g. allow 0-cost conversions, unless an invariant needs to be checked (like valid unicode).</p>
<p><code>Text</code> could maybe be a newytpe over <code>Bytes</code>. But that won’t actually save us a type. We still need the newtype to write an API that maintains the “valid unicode” invariant, which <code>Bytes</code> does not guarantee.</p>
<p>It is also hard to argue for the removal of the “short” types <code>ShortText</code> and <code>ShortByteString</code> as described in the section <a href="#slicable-vs-non-slicable">Slicable vs non-slicable</a>.</p>
<p>Writing a new string type from scratch can be really hard. But with the rich APIs of <code>ByteString</code>, <code>ShortByteString</code> and <code>Bytes</code>, coming up with newtypes might not be that difficult.</p>
<h3 id="what-are-we-missing">What are we missing</h3>
<p>We don’t have types for:</p>
<ul>
<li>Unicode Scalar Values (away with those surrogates)</li>
<li>Grapheme Clusters</li>
</ul>
<p>Especially the latter is something that seems to be potentially useful. We don’t just want to know the boundaries of Unicode code points, but of the actual user visible symbols, don’t we? The <code>text-icu</code> package seems to have an <a href="https://hackage.haskell.org/package/text-icu-0.8.0.5/docs/Data-Text-ICU.html#v:breakCharacter">API for breaking on grapheme boundaries</a>, but it doesn’t look very straight forward. I must admit I haven’t looked very hard though.</p>
<p>We also don’t have a good streaming solution in base. And maybe we never will. But that, probably, also means we will never get rid of lazy IO, which is a foot-gun for newcomers and everyone else.</p>
<p>My next project is likely going to be strongly typed filepaths, which <a href="https://hackage.haskell.org/package/hpath">do</a> <a href="https://hackage.haskell.org/package/path">already</a> <a href="https://hackage.haskell.org/package/strong-path">exist</a>, just not in combination with <code>OsPath</code>.</p>
<h2 id="special-thanks-to">Special thanks to</h2>
<ul>
<li>Andrew Lelechenko</li>
<li>Jonathan Knowles</li>
<li>Mike Pilgrem</li>
<li>John Ericson</li>
<li>streamly maintainers for their cutting edge API</li>
<li>all the text, bytestring, byteslice, short-text etc. maintainers</li>
<li>Other people I pinged about this topic</li>
</ul>
<h2 id="links-and-relevant-stuff">Links and relevant stuff</h2>
<h3 id="string-type-blog-posts">String type blog posts</h3>
<ul>
<li><a href="https://hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html">Fixing Haskell filepaths, by Julian Ospald</a></li>
<li><a href="https://www.fpcomplete.com/haskell/tutorial/string-types/">String types, by FPComplete</a></li>
<li><a href="https://free.cofree.io/2020/05/06/string-types/">Eat Haskell String Types for Breakfast, by Ziyang Liu</a></li>
<li><a href="https://mmhaskell.com/blog/2017/5/15/untangling-haskells-strings">Untangling Haskell’s Strings</a></li>
<li><a href="http://www.chriswarbo.net/blog/2020-06-08-haskell_strings.html">Haskell Strings, by Chris Warburton</a></li>
<li><a href="https://www.fpcomplete.com/haskell/library/vector/">vector: Efficient Packed-Memory Data Representations, by FPComplete</a></li>
</ul>
<h3 id="other-blog-posts">Other blog posts</h3>
<ul>
<li><a href="https://hasufell.github.io/posts/2021-10-22-conduit-to-streamly.html">From conduit to streamly</a></li>
<li><a href="https://chrisdone.com/posts/fast-haskell-c-parsing-xml/">Fast Haskell: Competing with C at parsing XML</a></li>
<li><a href="https://chrispenner.ca/posts/wc">Beating C With 80 Lines Of Haskell: Wc</a></li>
<li><a href="https://www.snoyman.com/blog/2021/03/haskell-base-proposal/">Haskell base proposal: unifying vector-like types</a></li>
<li><a href="https://www.snoyman.com/blog/2021/03/haskell-base-proposal-2/">Haskell base proposal, part 2: unifying vector-like types</a></li>
<li><a href="https://github.com/Bodigrim/my-talks/blob/master/zurihac2022/slides.pdf">The text package: finally with UTF-8, by Andrew Lelechenko</a></li>
</ul>
<h3 id="interesting-issues">Interesting issues</h3>
<ul>
<li><a href="https://github.com/haskell/bytestring/issues/193">Quit using ForeignPtr in favor of ByteArray#</a></li>
</ul>
<h3 id="string-types-not-discussed-here">String types not discussed here</h3>
<ul>
<li><a href="https://hackage.haskell.org/package/monoid-subclasses">monoid-subclasses</a></li>
<li><a href="https://hackage.haskell.org/package/vector-0.13.1.0/docs/Data-Vector.html">Data.Vector</a></li>
<li><a href="https://hackage.haskell.org/package/bytestring-0.12.1.0/docs/Data-ByteString-Builder.html">Data.ByteString.Builder</a> (not really a string type)</li>
<li><a href="https://hackage.haskell.org/package/ghc-9.8.2/docs/GHC-Data-FastString.html">GHC.Data.FastString</a></li>
<li><a href="https://hackage.haskell.org/package/jsaddle-0.9.9.0/docs/Data-JSString.html">Data.JSString</a></li>
</ul>]]></summary>
</entry>
<entry>
    <title>Getting your Haskell executable statically linked without Nix</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2024-04-21-static-linking.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2024-04-21-static-linking.html</id>
    <published>2024-04-21T00:00:00Z</published>
    <updated>2024-04-21T00:00:00Z</updated>
    <summary type="html"><![CDATA[<h2 id="motivation">Motivation</h2>
<p>Following the excellent post from Tom Sydney <a href="https://cs-syd.eu/posts/2024-04-20-static-linking-haskell-nix">“Getting your Haskell executable statically linked with Nix”</a>, I want to present an alternative approach.</p>
<p>I believe nix has questionable ergnomics and most Haskell developers don’t need it, even if they want to link their binaries statically.</p>
<h2 id="musl-and-alpine-linux">Musl and Alpine Linux</h2>
<p>GHC/cabal don’t really know how to do partial static linking, unless you employ some trickery. So we need a system where we can link everything statically, including libc. This leads us to the Musl libc, which has good support for static linking.</p>
<p>Two prominent choices for musl based Linux distributions are:</p>
<ul>
<li>Alpine Linux</li>
<li>Void Linux musl</li>
</ul>
<p>In this guide, we pick Alpine.</p>
<h2 id="ghcup-and-ghc">GHCup and GHC</h2>
<p>In order to use Alpine Linux as a build environment, we need proper toolchain support. GHCup supports Alpine Linux as a first class citizen, so you should be able to install GHC on Alpine. If you run into issues, <a href="https://github.com/haskell/ghcup-hs/issues/new">open a bug report</a>.</p>
<p>Note that you do not need a statically linked GHC to build a static binary. This is a misconception.</p>
<h2 id="build-environment">Build environment</h2>
<p>We need a clean build environment that is reproducible (-ish). We can use docker, which has excellent support for Alpine Linux containers.</p>
<h2 id="tying-everything-together">Tying everything together</h2>
<p>To tie everything together, we start an interactive shell in a docker container:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a>$ <span class="ex">docker</span> run --rm -ti alpine:3.19 sh</span></code></pre></div>
<p>Then we install pre-requisites:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a>$ <span class="ex">apk</span> update</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true"></a>$ <span class="ex">apk</span> add curl gcc g++ git gmp-dev libc-dev libffi-dev make musl-dev ncurses-dev perl tar xz</span></code></pre></div>
<p>We install GHCup:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a>$ <span class="ex">curl</span> --proto <span class="st">&#39;=https&#39;</span> --tlsv1.2 -sSf https://get-ghcup.haskell.org <span class="kw">|</span> <span class="va">BOOTSTRAP_HASKELL_NONINTERACTIVE=</span>1 <span class="fu">sh</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="bu">source</span> ~/.ghcup/env</span></code></pre></div>
<p>Let’s create a dummy app:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a>$ <span class="fu">mkdir</span> test-app</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a>$ <span class="bu">cd</span> test-app</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a>$ <span class="ex">cabal</span> init --non-interactive</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a>$ <span class="ex">cabal</span> build --enable-executable-static</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a>$ <span class="fu">mkdir</span> out/</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a>$ <span class="fu">cp</span> <span class="va">$(</span><span class="ex">cabal</span> -v0 list-bin exe:test-app<span class="va">)</span> out/</span></code></pre></div>
<p>We use <code>cabal build</code> in combination with <code>cabal list-bin</code>, because some versions of cabal are buggy when combining <code>--enable-executable-static</code> with <code>install</code>: <a href="https://github.com/haskell/cabal/pull/9697">https://github.com/haskell/cabal/pull/9697</a></p>
<p>It is also possible to pass <code>-ghc-options='-optl-static'</code> instead of <code>--enable-executable-static</code>.</p>
<p>Now we examine the binary:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a>$ <span class="ex">apk</span> add file</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a>$ <span class="fu">file</span> out/test-app</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a><span class="ex">out</span>/test-app: <span class="ex">ELF</span> 64-bit LSB executable, x86-64, version 1 (SYSV), <span class="ex">statically</span> linked, BuildID[sha1]=ab54deda534ac8065f5e263e84f168fb46eb8227, with debug_info, not stripped</span></code></pre></div>
<p>That looks good.</p>
<h2 id="linking-against-system-libraries">Linking against system libraries</h2>
<p>If your binary depends on system C libraries, you will need to install those packages. E.g. if you link against zlib, you need the <code>-dev</code> and sometimes <code>-static</code> packages:</p>
<pre><code>apk add zlib-dev zlib-static</code></pre>
<p>You can search for libraries and installed files at <a href="https://pkgs.alpinelinux.org/packages">https://pkgs.alpinelinux.org/packages</a></p>
<h2 id="github-ci">Github CI</h2>
<p>Examples of Github actions using alpine and building static release binaries can be found here:</p>
<ul>
<li><a href="https://github.com/haskell/ghcup-hs/blob/master/.github/workflows/release.yaml">https://github.com/haskell/ghcup-hs/blob/master/.github/workflows/release.yaml</a></li>
<li><a href="https://github.com/hasufell/stack2cabal/blob/master/.github/workflows/release.yaml">https://github.com/hasufell/stack2cabal/blob/master/.github/workflows/release.yaml</a></li>
<li><a href="https://github.com/stable-haskell/cabal/blob/cabal-install-v3.10.3.0/.github/workflows/release.yaml">https://github.com/stable-haskell/cabal/blob/cabal-install-v3.10.3.0/.github/workflows/release.yaml</a></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>This approach has been used in GHCup since its rewrite in Haskell. It has worked very well.</p>
<p>The only downside is that you rely on Alpine Linux packaging of system C libraries. If you link to a package that is not in the Alpine repos, you will need more manual work.</p>
<p>In that case it might be worthwhile to check Void Linux as an alternative.</p>]]></summary>
</entry>
<entry>
    <title>GHCup is not an installer</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2023-11-14-ghcup-is-not-an-installer.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2023-11-14-ghcup-is-not-an-installer.html</id>
    <published>2023-11-14T00:00:00Z</published>
    <updated>2023-11-14T00:00:00Z</updated>
    <summary type="html"><![CDATA[<h2 id="misunderstandings">Misunderstandings</h2>
<p>Over the past few years, there have been recurring questions or misunderstandings about GHCup. E.g.:</p>
<ul>
<li>GHCup only installs bindists from upstream (e.g. GHC/Cabal/HLS CI)</li>
<li>GHCup never applies patches to tools it distributes</li>
</ul>
<p>Both those assumptions do not apply, because GHCup is primarily a <strong>distribution channel</strong>, not just an installer. The distribution channel is basically the <a href="https://github.com/haskell/ghcup-metadata/blob/develop/ghcup-0.0.8.yaml"><code>ghcup-0.0.8.yaml</code> metadata file</a>.</p>
<p>Users who strictly only want upstream bindists (whether they’re broken or not) can use a different distribution channel and opt out of all unofficial things: <a href="https://github.com/haskell/ghcup-metadata/blob/develop/ghcup-vanilla-0.0.8.yaml"><code>ghcup-vanilla-0.0.8.yaml</code></a>. More information in the <a href="https://github.com/haskell/ghcup-metadata#for-end-users">README</a>.</p>
<h2 id="policies-and-priorities">Policies and priorities</h2>
<p>GHCup follows similar philosophies and policies like Debian or Fedora. Most of them are outlined <a href="https://www.haskell.org/ghcup/about/#distribution-policies">here</a> in more details. The most important points, simplified, are:</p>
<ol type="1">
<li>The end-user experience is our primary concern</li>
<li>We strive to collaborate with all maintainers of all the tools we support and maintain a good relationship</li>
<li>We may fix build system or other distribution bugs in upstream bindists</li>
<li>We may even patch source code of supported tools in very rare cases if that is required to ensure that the end-user experience does not break</li>
</ol>
<p>As such, we prioritize the end-user over interests of upstream developers. E.g. it frequently happens that upstream developers want a new release to be ‘recommended’ (that is: installed by default if you run GHCup for the first time). However, experience shows that it’s usually better to wait.</p>
<p>So far, we have never patched source code. In case that ever happens, it would be communicated to both upstream and the end user via post-install messages.</p>
<h2 id="affects-on-maintenance">Affects on maintenance</h2>
<p>Following these priorities, the smallest part of GHCup maintenance sometimes seems to be the codebase. The following tasks have come up repeatedly, until <a href="https://github.com/haskell/ghcup-hs/issues/848">I decreased my workload considerably to avoid a proper burnout</a>:</p>
<ul>
<li>building unofficial bindists for missing platforms (e.g. GHC alpine i386, armv7, FreeBSD or stack darwin aarch64)</li>
<li>patching upstream bindists in case of issues discovered post-release</li>
<li>tracking releases and bugs of all tools to decide which release is to be ‘recommended’</li>
<li>being involved in CI code and release issues of most tools</li>
<li>meetings and communication with HF, GHC HQ, other tooling maintainers and especially HLS</li>
<li>developing and supporting new ideas (dynamic HLS bindists, GHC nightlies, …)</li>
<li>advocating and pushing for prioritizing end user experience, e.g. <a href="https://github.com/haskellfoundation/tech-proposals/issues/48">here</a></li>
<li>supporting users having installation issues via IRC, Discord, email, different issue trackers, …</li>
</ul>
<p>Most of this has now stalled, until GHCup gets more support (e.g. <a href="https://discourse.haskell.org/t/haskell-foundation-october-2023-update/8054#ghcup-backup-maintenance-5">from Obsidian</a>, which I’m excited about).</p>
<h2 id="possible-future">Possible future</h2>
<p>GHCup being a distribution channel also means that, theoretically, we might completely stop relying on upstream bindists and roll our own. For this idea I already have prepared <a href="https://gist.github.com/hasufell/18cb5438ce7c2ba388160588d751b32d">a document about Midstream bindists</a> that could be submitted as a HF tech proposal. As I don’t have the capacity, I have not submitted it yet and maybe I never will.</p>
<p>In a perfect world, we want full control over the bindists, decide on the provided configurations, distribution support, platform support, etc.</p>
<p>This is what Linux distributions do too. They rarely use upstream bindists, except for bootstrapping purposes.</p>
<h2 id="what-we-want-from-upstream-is-not-bindists">What we want from upstream is not bindists</h2>
<p>What distributors really want from upstream is not bindists, but something else:</p>
<ul>
<li>feasibility to run test suites on the end-users system (that’s where it matters, not just in CI)
<ul>
<li>and have processes and mechanisms to get feedback for failing test suites (send report via cli that gets aggregated and analyzed somewhere)</li>
</ul></li>
<li>awareness that the build system is not just a dev tool for hackers, but an interface for distributors and end users</li>
<li>mindfulness about platform support (including less common ones)</li>
<li>not relying on hermetically built binaries: instead make sure a manually compiled binary works on all platforms and have sufficient mechanisms to detect when it wouldn’t (through bindist configure, runtime checks, test suite, …)</li>
<li>have prereleases as much as possible, including minor releases</li>
<li>communicate everything that potentially impacts distributors</li>
<li>longer patch/security maintenance windows for older versions</li>
</ul>
<p>If the build system interface was stable, we could easily use <code>ghcup compile ghc</code> in our own CI, followed by a <code>ghcup test ghc</code> (yes, that exists!) and be done. Then tell end users to utilize <code>ghcup test ghc</code> after installation to make sure it really works with their environment (that’s not a given even with official bindists). However, the test suite is flaky and the test bindists are buggy and not very portable, so this goal is far out.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope that this clears up some of the confusion and expectations and that end users understand that they have a choice by utilizing different <a href="https://github.com/haskell/ghcup-metadata#metadata-variants-distribution-channels">metadata files</a>.</p>]]></summary>
</entry>
<entry>
    <title>Fixing 'FilePath' in Haskell</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2022-06-29-fixing-haskell-filepaths.html</id>
    <published>2022-06-29T00:00:00Z</published>
    <updated>2022-06-29T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>I’m pleased to announce that the Haskell type <code>type FilePath = String</code> has a successor, which was first discussed many years ago as the <a href="https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path">Abstract FilePath proposal (AFPP)</a>.</p>
<p>The new type shipped with the <a href="https://hackage.haskell.org/package/filepath-1.4.100.0">filepath-1.4.100.0</a> package is:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="co">-- * Path types</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true"></a><span class="co">-- | FilePath for windows.</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">WindowsPath</span> <span class="ot">=</span> <span class="dt">WindowsString</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true"></a><span class="co">-- | FilePath for posix systems.</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">PosixPath</span> <span class="ot">=</span> <span class="dt">PosixString</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true"></a><span class="co">-- | Abstract filepath, depending on current platform.</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true"></a><span class="co">-- Matching on the wrong constructor is a compile-time error.</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">OsPath</span> <span class="ot">=</span> <span class="dt">OsString</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true"></a></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true"></a></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true"></a><span class="co">-- * String types</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true"></a><span class="co">-- Constructors are not public API.</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true"></a></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">WindowsString</span> <span class="ot">=</span> <span class="dt">WindowsString</span> <span class="dt">ShortByteString</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true"></a></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">PosixString</span> <span class="ot">=</span> <span class="dt">PosixString</span> <span class="dt">ShortByteString</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true"></a></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true"></a><span class="kw">newtype</span> <span class="dt">OsString</span> <span class="ot">=</span> <span class="dt">OsString</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true"></a><span class="pp">#if defined(mingw32_HOST_OS)</span></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true"></a>  <span class="dt">WindowsString</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true"></a><span class="pp">#else</span></span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true"></a>  <span class="dt">PosixString</span></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true"></a><span class="pp">#endif</span></span></code></pre></div>
<p>The reason we have two sets of types here is simply to maintain the current weak distinction in filepath for functions that deal with not-quite-filepaths, e.g.: <code>splitSearchPath :: String -&gt; [FilePath]</code>. This also allows us to provide slightly different API (e.g. QuasiQuoter for <code>OsString</code> differs from <code>OsPath</code>). OsPath is not a newtype, because it doesn’t provide any additional guarantees over OsString. ‘filepath’ remains a low-level library and does not provide strong guarantees for filepaths (such as validity).</p>
<p>Libraries with stronger filepath guarantees are listed in the <a href="https://gitlab.haskell.org/haskell/filepath/-/blob/master/README.md#what-is-a-filepath">README</a>.</p>
<p>Unlike the original proposal, this is <strong>additional API (not part of <code>base</code>) and will not break any existing code</strong>. Core libraries are expected to upgrade their API and provide additional variants that support this new type. Migration strategies are discussed further down. The ecosystem might need some time to migrate. This is also a <a href="#how-to-help">call for help</a>!</p>
<p>But let’s look at the reasons why <code>String</code> is problematic first.</p>
<h2 id="toc">TOC</h2>
<ul>
<li><a href="#whats-wrong-with-string">What’s wrong with String?</a></li>
<li><a href="#the-solution">The solution</a></li>
<li><a href="#how-to-use-the-new-api">How to use the new API</a></li>
<li><a href="#migration-for-library-authors">Migration for library authors</a>
<ul>
<li><a href="#1-drop-string-based-api-and-just-provide-ospath">1. drop String based API and just provide OsPath</a></li>
<li><a href="#2-provide-a-shim-compatibility-api-for-string">2. provide a shim compatibility API for String</a></li>
<li><a href="#3-using-cpp-to-export-two-apis">3. using CPP to export two APIs</a></li>
<li><a href="#accessing-the-raw-bytes-in-a-cross-platform-manner">Accessing the raw bytes in a cross-platform manner</a></li>
</ul></li>
<li><a href="#history-of-the-proposal">History of the proposal</a></li>
<li><a href="#contributors">Contributors</a></li>
<li><a href="#patch-load">Patch load</a></li>
<li><a href="#how-to-help">How to help</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
<h2 id="whats-wrong-with-string">What’s wrong with String?</h2>
<p>Filepaths are resources on the (users) system. We create, delete, copy them. Any corner case with filepaths can have devastating effects: deleting the wrong file, comparing the wrong files, failing whitelists, security bugs, etc.</p>
<p>To recap, the definition of String is:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="kw">type</span> <span class="dt">String</span> <span class="ot">=</span> [<span class="dt">Char</span>]</span></code></pre></div>
<p>So a String is a list of <code>Char</code>. And <code>Char</code> is encoded as UTF-8, right? Unfortunately not, it’s a <em>Unicode code point</em>.</p>
<p>A unicode code point is an integer in the <em>Unicode codespace</em>. The <a href="https://www.unicode.org/versions/Unicode14.0.0/ch03.pdf#G2212">standard</a> gets a little technical here, but let’s just say UTF-8 is one of many encodings of <code>[Char]</code>.</p>
<p>That out of the way, let’s look at how filepaths are actually represented on the system level.</p>
<p>On windows, filepaths are just <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/76f10dd8-699d-45e6-a53c-5aefc586da20"><em>wide character</em> arrays</a> (<code>wchar_t*</code>, so basically <code>[Word16]</code>). On unix, filepaths are <a href="https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170"><em>character</em> arrays</a> (<code>char[]</code>, so basically <code>[Word8]</code>).</p>
<p>In both cases, there’s no encoding specified, although on windows we can <em>mostly</em> assume UTF-16LE. So… to go from <code>String</code> to <code>CString</code>/<code>CWString</code> at the outer FFI layer, we need to make a decision.</p>
<p><code>base</code> currently does the following:</p>
<ol type="1">
<li>On unix, it uses <a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/GHC-IO-Encoding.html#v:getFileSystemEncoding"><code>getFileSystemEncoding</code></a> and <a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/GHC-IO-Encoding.html#v:mkTextEncoding"><code>mkTextEncoding</code></a> to pick a round-trippable encoding for filepaths. E.g. if your locale returns <code>en_US.UTF-8</code> you’ll get <code>UTF-8//ROUNDTRIP</code> TextEncoding, which is based on <a href="https://peps.python.org/pep-0383/">PEP 383</a> and invalid bytes get translated to some special representation (lone surrogates) in order to be roundtripped.</li>
<li>On windows, it uses a <a href="https://gitlab.haskell.org/ghc/ghc/-/blob/0e22f16cda8468256b4c5d04214276be30e23faa/libraries/base/Foreign/C/String.hs#L423-444">private permissive UTF-16 encoding</a> that allows to roundtrip coding errors as well.</li>
</ol>
<p>Windows isn’t too problematic here. The encoding is total. However, on unix, the interpretation of filepaths depends on the <em>currently set locale</em>. This is wrong for a number of reasons:</p>
<ol type="1">
<li>there’s no guarantee that the currently set locale corresponds to the encoding of a specific filepath (the filepath could be on a USB drive that has a japanese encoding, such as <code>CP932</code>)</li>
<li>as the documentation of <a href="https://hackage.haskell.org/package/base-4.16.1.0/docs/GHC-IO-Encoding.html#v:mkTextEncoding">mkTextEncoding</a> says, only very specific encodings actually roundtrip properly (<code>CP932</code> does not)</li>
<li>on conversion to <code>String</code>, you “lose” the underlying encoding and may end up with weirdly escaped Unicode codepoints. Roundtripping can break if a call to <code>setFileSystemEncoding</code> interleaves the conversions.</li>
<li>it’s hard to get the original bytes back… this may have security implications for e.g. filepath whitelists</li>
</ol>
<p>So, how do other languages solve this? Python simply enforces <code>UTF-8</code> (with PEP 383 escaping) on unix. That makes the roundtripping almost sound. But this comes with its own set of problems:</p>
<ol type="1">
<li>if the underlying filepath is not UTF-8, the <code>[Char]</code> representation is lossless (from <code>CString</code> to <code>[Char]</code>), but may be somewhat non-sensical for further interpretation, because you might have excessive escaping or your <code>Char</code>s don’t correspond to what the user sees on their system</li>
<li>this has really bad interoperability, because the roundtrip encoding can in fact produce invalid UTF-8. The unicode consortium itself has <a href="https://unicode.org/L2/L2009/09236-pep383-problems.html">voiced their concerns with this approach</a></li>
<li>since Haskell <code>Char</code> also includes surrogates, the conversion from <code>String</code> to e.g. UTF-8 <code>CString</code> can in fact fail, so is not total</li>
</ol>
<p>I have assembled a <a href="https://gist.github.com/hasufell/c600d318bdbe010a7841cc351c835f92">list of correctness issues</a> with these approaches for in-depth reading.</p>
<h2 id="the-solution">The solution</h2>
<p>Just stop converting filepaths!</p>
<p>We can just keep the original bytes from the system API. Many filepath operations actually don’t need to know the exact underlying encoding. E.g. the filepath separator <code>/</code> on unix is a pre-defined <em>byte</em> (<code>0x2F</code>). You can just scan the byte array for this byte. The position doesn’t matter, the encoding doesn’t matter. File <strong>names</strong> <em>cannot</em> include this byte, period.</p>
<p>However, since unix and windows are different (<code>[Word8]</code> vs <code>[Word16]</code>), any API that deals with low-level filepaths in a cross-platform manner needs to understand this and write correct code. More on this in the migration strategy section below.</p>
<p>We decided to use <code>ShortByteString</code> as the internal representation of filepaths, because:</p>
<ol type="1">
<li>these are raw, uninterpreted bytes, a wrapper around <code>ByteArray#</code>, which has many efficient primops</li>
<li>it’s unpinned, so doesn’t contribute to memory fragmentation (<a href="https://github.com/hasufell/filepath-debug/blob/master/result.txt">proof</a>)</li>
<li>provides convenient API via <code>bytestring</code>, which has been <a href="https://github.com/haskell/bytestring/pull/471">greatly enhanced as part of this proposal</a></li>
</ol>
<p>So, in general the idea is to avoid dealing with <code>String</code> at all. There may still be use cases for String though, e.g.:</p>
<ol type="1">
<li>dealing with legacy APIs</li>
<li>reading filepaths from a UTF-8 encoded text file (you probably want <code>Text</code> here, but it’s trivial to convert to String)</li>
<li>a unified representation across platforms (e.g. to send over the wire or to serialize)</li>
</ol>
<h2 id="how-to-use-the-new-api">How to use the new API</h2>
<p>Many examples are here: <a href="https://github.com/hasufell/filepath-examples">https://github.com/hasufell/filepath-examples</a></p>
<p>Note that not all libraries have released support for the new API yet, so have a look at this <a href="https://github.com/hasufell/filepath-examples/blob/master/cabal.project">cabal.project</a> if you want to start right away. Generally, you should be able to use these packages already:</p>
<ul>
<li><strong>filepath</strong>: provides filepath manipulation and the new <code>OsPath</code> type</li>
<li><strong>unix</strong>: provides new API variants, e.g. <code>System.Posix.Files.PosixString</code> (as an alternative to <code>System.Posix.Files</code>)</li>
<li><strong>Win32</strong>: similarly, provides new variants, e.g. <code>System.Win32.WindowsString.File</code></li>
<li><strong>directory</strong>: provides the new API under <code>System.Directory.OsPath</code></li>
<li><a href="https://github.com/hasufell/file-io"><strong>file-io</strong></a>: companion package that provides base-like file reading/writing/opening operations</li>
</ul>
<p>Most end-users developing applications should be able to convert to the new API with little effort, given that their favorite libraries already support this new type.</p>
<p><a href="https://hackage.haskell.org/package/filepath-1.4.100.0/docs/System-OsPath.html">System.OsPath</a> exports the same API as <code>System.FilePath</code> with some additional helpers to convert from and to <code>String</code>. Likewise <code>System.OsPath.Posix</code>/<code>System.OsPath.Windows</code> are equivalent to <code>System.FilePath.Posix</code>/<code>System.FilePath.Windows</code>.</p>
<p>So, you can just:</p>
<ol type="1">
<li>update your dependencies lower bounds to the minimum version that supports <code>OsPath</code> (might need <a href="https://github.com/hasufell/filepath-examples/blob/master/cabal.project">source-repository-package</a> stanzas)</li>
<li>for <code>filepath</code> import <code>System.OsPath</code> instead of <code>System.FilePath</code></li>
<li>use the specialised API from your dependencies (e.g. for unix <code>System.Posix.Directory.PosixPath</code> instead of <code>System.Posix.Directory</code>)</li>
<li>to write OsPath literals, use the provided <a href="https://hackage.haskell.org/package/filepath-1.4.100.0/docs/System-OsPath.html#v:osp">QuasiQuoters</a>. There’s no <code>IsString</code> instance, see the <a href="#why-is-there-no-isstring-instance-overloadedstrings">faq</a>.</li>
<li>if you’re just using an ASCII subset or strict unicode scalar values, you can use <code>fromJust . encodeUtf</code> and <code>fromJust . decodeUtf</code> to pack/unpack literals</li>
<li>since <code>base</code> doesn’t support this new type, you’ll need the already mentioned companion library <a href="https://github.com/hasufell/file-io">file-io</a> for opening a <code>Handle</code> and writing/reading files</li>
<li>if you use legacy APIs that still use <code>FilePath</code>, there are <a href="https://github.com/hasufell/filepath-examples/blob/master/examples/Process.hs">examples</a> on how to deal with them (usually <code>System.OsPath.encodeFS</code> and <code>System.OsPath.decodeFS</code>)</li>
</ol>
<p>A table for encoding/decoding strategies follows:</p>
<table>
<colgroup>
<col style="width: 17%" />
<col style="width: 8%" />
<col style="width: 6%" />
<col style="width: 27%" />
<col style="width: 26%" />
<col style="width: 14%" />
</colgroup>
<thead>
<tr class="header">
<th>API function</th>
<th>from</th>
<th>to</th>
<th>posix encoding</th>
<th>windows encoding</th>
<th>remarks</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>encodeUtf</code></td>
<td>FilePath</td>
<td>OsPath</td>
<td>UTF-8 (strict)</td>
<td>UTF-16 (strict)</td>
<td>not total</td>
</tr>
<tr class="even">
<td><code>encodeWith</code></td>
<td>FilePath</td>
<td>OsPath</td>
<td>user specified</td>
<td>user specified</td>
<td>depends on input</td>
</tr>
<tr class="odd">
<td><code>encodeFS</code></td>
<td>FilePath</td>
<td>OsPath</td>
<td>depends on getFileSystemEncoding</td>
<td>UTF-16 (escapes coding errors)</td>
<td>requires IO, used by <code>base</code> for roundtripping</td>
</tr>
<tr class="even">
<td><code>decodeUtf</code></td>
<td>OsPath</td>
<td>FilePath</td>
<td>UTF-8 (strict)</td>
<td>UTF-16 (strict)</td>
<td>not total</td>
</tr>
<tr class="odd">
<td><code>decodeWith</code></td>
<td>OsPath</td>
<td>FilePath</td>
<td>user specified</td>
<td>user specified</td>
<td>depends on input</td>
</tr>
<tr class="even">
<td><code>decodeFS</code></td>
<td>OsPath</td>
<td>FilePath</td>
<td>depends on getFileSystemEncoding</td>
<td>UTF-16 (escapes coding errors)</td>
<td>requires IO, used by <code>base</code> for roundtripping</td>
</tr>
</tbody>
</table>
<p>These conversions are particularly useful if you’re dealing with legacy API that is still <code>FilePath</code> based. An example on how to do that with the process package is <a href="https://github.com/hasufell/filepath-examples/blob/master/examples/Process.hs">here</a>.</p>
<h2 id="migration-for-library-authors">Migration for library authors</h2>
<p>Core libraries or other libraries exporting API that is heavy on filepaths generally have 3 options:</p>
<h3 id="drop-string-based-api-and-just-provide-ospath">1. drop String based API and just provide OsPath</h3>
<p>This is feasible, because users can themselves convert via <code>System.OsPath.encodeFS</code> and <code>System.OsPath.decodeFS</code> to and from <code>String</code>.</p>
<h3 id="provide-a-shim-compatibility-api-for-string">2. provide a shim compatibility API for String</h3>
<p>This is what this <code>directory</code> PR does: <a href="https://github.com/haskell/directory/pull/136/files">https://github.com/haskell/directory/pull/136/files</a>… see <code>System/Directory.hs</code>.</p>
<p>The idea is to write the core against <code>OsPath</code> and then create a <code>String</code> based API that wraps the core via <code>System.OsPath.encodeFS</code> and <code>System.OsPath.decodeFS</code> to mimic behavior of <code>base</code>. This usually requires IO, though.</p>
<h3 id="using-cpp-to-export-two-apis">3. using CPP to export two APIs</h3>
<p>This is what filepath itself does. It contains an <a href="https://gitlab.haskell.org/haskell/filepath/-/blob/master/System/FilePath/Internal.hs">abstract module</a>, which is then imported while setting specific types and platform information (<a href="https://gitlab.haskell.org/haskell/filepath/-/blob/master/System/OsPath/Posix/Internal.hs">PosixPath</a>, <a href="https://gitlab.haskell.org/haskell/filepath/-/blob/master/System/OsPath/Windows/Internal.hs">WindowsPath</a>, <a href="https://gitlab.haskell.org/haskell/filepath/-/blob/master/System/FilePath/Posix.hs">System.FilePath.Posix</a> and <a href="https://gitlab.haskell.org/haskell/filepath/-/blob/master/System/FilePath/Windows.hs">System.FilePath.Windows</a>).</p>
<p>The main trick here is to not use any String based API (e.g. no pattern matching or use of <code>:</code>). Instead, we only use <code>uncons</code>/<code>unsnoc</code>, <code>head</code>/<code>last</code> etc, so the intersection of String and ShortByteString APIs… and then adjust the imports based on the type.</p>
<p>E.g. the following code:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="ot">splitSearchPath ::</span> <span class="dt">String</span> <span class="ot">-&gt;</span> [<span class="dt">FilePath</span>]</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a>splitSearchPath <span class="ot">=</span> f</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a>    <span class="kw">where</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a>    f xs <span class="ot">=</span> <span class="kw">case</span> <span class="fu">break</span> isSearchPathSeparator xs <span class="kw">of</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a>           (pre, []    ) <span class="ot">-&gt;</span> g pre</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a>           (pre, _<span class="op">:</span>post) <span class="ot">-&gt;</span> g pre <span class="op">++</span> f post</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true"></a></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true"></a>    g <span class="st">&quot;&quot;</span> <span class="ot">=</span> [<span class="st">&quot;.&quot;</span> <span class="op">|</span> isPosix]</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true"></a>    g (<span class="ch">&#39;\&quot;&#39;</span><span class="op">:</span>x<span class="op">@</span>(_<span class="op">:</span>_)) <span class="op">|</span> isWindows <span class="op">&amp;&amp;</span> <span class="fu">last</span> x <span class="op">==</span> <span class="ch">&#39;\&quot;&#39;</span> <span class="ot">=</span> [<span class="fu">init</span> x]</span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true"></a>    g x <span class="ot">=</span> [x]</span></code></pre></div>
<p>became:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="ot">splitSearchPath ::</span> <span class="dt">STRING</span> <span class="ot">-&gt;</span> [<span class="dt">FILEPATH</span>]</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a>splitSearchPath <span class="ot">=</span> f</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a>    <span class="kw">where</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a>    f xs <span class="ot">=</span> <span class="kw">let</span> (pre, post) <span class="ot">=</span> <span class="fu">break</span> isSearchPathSeparator xs</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a>           <span class="kw">in</span> <span class="kw">case</span> uncons post <span class="kw">of</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a>             <span class="dt">Nothing</span>     <span class="ot">-&gt;</span> g pre</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a>             <span class="dt">Just</span> (_, t) <span class="ot">-&gt;</span> g pre <span class="op">++</span> f t</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true"></a></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true"></a>    g x <span class="ot">=</span> <span class="kw">case</span> uncons x <span class="kw">of</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true"></a>      <span class="dt">Nothing</span> <span class="ot">-&gt;</span> [singleton _period <span class="op">|</span> isPosix]</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true"></a>      <span class="dt">Just</span> (h, t)</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true"></a>        <span class="op">|</span> h <span class="op">==</span> _quotedbl</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true"></a>        , (<span class="dt">Just</span> _) <span class="ot">&lt;-</span> uncons t <span class="co">-- &gt;= 2</span></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true"></a>        , isWindows</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true"></a>        , (<span class="dt">Just</span> (i, l)) <span class="ot">&lt;-</span> unsnoc t</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true"></a>        , l <span class="op">==</span> _quotedbl <span class="ot">-&gt;</span> [i]</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true"></a>        <span class="op">|</span> <span class="fu">otherwise</span> <span class="ot">-&gt;</span> [x]</span></code></pre></div>
<p>The windows include site is something like:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="co">-- word16 based bytestring functions</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsPath.Data.ByteString.Short.Word16</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a><span class="co">-- defining types</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a><span class="pp">#define FILEPATH ShortByteString</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true"></a><span class="pp">#define WINDOWS</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true"></a><span class="co">-- include the CPP module</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true"></a><span class="pp">#include &quot;Internal.hs&quot;</span></span></code></pre></div>
<p>Then we can have a <code>WindowsPath</code>/<code>PosixPath</code>/<code>OsPath</code> wrappers:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true"></a><span class="ot">splitPath ::</span> <span class="dt">FILEPATH_NAME</span> <span class="ot">-&gt;</span> [<span class="dt">FILEPATH_NAME</span>]</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a>splitPath (<span class="dt">OSSTRING_NAME</span> bs) <span class="ot">=</span> <span class="dt">OSSTRING_NAME</span> <span class="op">&lt;$&gt;</span> C.splitPath bs</span></code></pre></div>
<p>And that is included like so:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsPath.Types</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString.Windows</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsPath.Windows.Internal</span> <span class="kw">as</span> <span class="dt">C</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true"></a></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true"></a><span class="pp">#define FILEPATH_NAME WindowsPath</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true"></a><span class="pp">#define WINDOWS</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true"></a></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true"></a><span class="pp">#include &quot;PathWrapper.hs&quot;</span></span></code></pre></div>
<p>Not very pretty, but avoids a lot of repetition and doesn’t require a partial wrapper layer that converts between <code>ShortByteString</code> and <code>String</code>.</p>
<h3 id="accessing-the-raw-bytes-in-a-cross-platform-manner">Accessing the raw bytes in a cross-platform manner</h3>
<p>Some libraries might need access to the raw bytes of the filepaths, e.g. because the <code>filepath</code> API is insufficient. It’s important to understand that on unix, we’re basically dealing with <code>[Word8]</code> and on windows with <code>[Word16]</code>, where both lists are represented as a compact <code>ShortByteString</code>.</p>
<p>E.g. a cross-platform function might look like this:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true"></a><span class="kw">module</span> <span class="dt">MyModule</span> <span class="kw">where</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsPath.Types</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true"></a><span class="kw">import</span> <span class="dt">System.OsString.Internal.Types</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true"></a><span class="pp">#if defined(mingw32_HOST_OS)</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true"></a><span class="co">-- word 16 based windows API</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsPath.Data.ByteString.Short.Word16</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true"></a>       as <span class="dt">SBS</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsPath.Windows</span> <span class="kw">as</span> <span class="dt">PFP</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true"></a><span class="pp">#else</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true"></a><span class="co">-- word 8 based posix API</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsPath.Data.ByteString.Short</span> <span class="kw">as</span> <span class="dt">SBS</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">System.OsPath.Posix</span> <span class="kw">as</span> <span class="dt">PFP</span></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true"></a><span class="pp">#endif</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true"></a></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true"></a><span class="ot">crossPlatformFunction ::</span> <span class="dt">OsPath</span> <span class="ot">-&gt;</span> <span class="dt">IO</span> ()</span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true"></a><span class="pp">#if defined(mingw32_HOST_OS)</span></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true"></a>crossPlatformFunction (<span class="dt">OsString</span> pfp<span class="op">@</span>(<span class="dt">WindowsString</span> ba)) <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true"></a>    <span class="co">-- use filepath functions for windows specific</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true"></a>    <span class="co">-- operating system strings</span></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true"></a>    <span class="kw">let</span> ext <span class="ot">=</span> PFP.takeExtension pfp</span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true"></a>    <span class="co">-- operate directly on the underlying bytestring</span></span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true"></a>    <span class="co">-- (which is a wide character bytestring, so uses Word16)</span></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true"></a>    <span class="kw">let</span> foo <span class="ot">=</span> SBS.takeWhile</span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true"></a>    <span class="op">...</span></span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true"></a><span class="pp">#else</span></span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true"></a>crossPlatformFunction (<span class="dt">OsString</span> pfp<span class="op">@</span>(<span class="dt">PosixString</span> ba)) <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true"></a>    <span class="co">-- use filepath functions for posix specific</span></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true"></a>    <span class="co">-- operating system strings</span></span>
<span id="cb8-30"><a href="#cb8-30" aria-hidden="true"></a>    <span class="kw">let</span> ext <span class="ot">=</span> PFP.takeExtension pfp</span>
<span id="cb8-31"><a href="#cb8-31" aria-hidden="true"></a>    <span class="co">-- operate directly on the underlying bytestring</span></span>
<span id="cb8-32"><a href="#cb8-32" aria-hidden="true"></a>    <span class="co">-- (which is just Word8 bytestring)</span></span>
<span id="cb8-33"><a href="#cb8-33" aria-hidden="true"></a>    <span class="kw">let</span> foo <span class="ot">=</span> SBS.takeWhile</span>
<span id="cb8-34"><a href="#cb8-34" aria-hidden="true"></a>    <span class="op">...</span></span>
<span id="cb8-35"><a href="#cb8-35" aria-hidden="true"></a><span class="pp">#endif</span></span></code></pre></div>
<h2 id="history-of-the-proposal">History of the proposal</h2>
<ol type="1">
<li>first wiki proposal: <a href="https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path">https://gitlab.haskell.org/ghc/ghc/-/wikis/proposal/abstract-file-path</a></li>
<li>Revival attempts
<ul>
<li><a href="https://discourse.haskell.org/t/reviving-the-abstract-filepath-proposal-afpp-in-user-space/2344">https://discourse.haskell.org/t/reviving-the-abstract-filepath-proposal-afpp-in-user-space/2344</a></li>
<li><a href="https://mail.haskell.org/pipermail/libraries/2021-August/031427.html">https://mail.haskell.org/pipermail/libraries/2021-August/031427.html</a></li>
<li><a href="https://groups.google.com/g/haskell-core-libraries/c/WzapcSvvfQM/m/oDhGbf9wCwAJ?pli=1">https://groups.google.com/g/haskell-core-libraries/c/WzapcSvvfQM/m/oDhGbf9wCwAJ?pli=1</a></li>
</ul></li>
<li>PRs:
<ul>
<li><a href="https://gitlab.haskell.org/haskell/filepath/-/merge_requests/103">https://gitlab.haskell.org/haskell/filepath/-/merge_requests/103</a></li>
<li><a href="https://github.com/haskell/bytestring/pull/471">https://github.com/haskell/bytestring/pull/471</a></li>
<li><a href="https://github.com/haskell/win32/pull/198">https://github.com/haskell/win32/pull/198</a></li>
<li><a href="https://github.com/haskell/unix/pull/202">https://github.com/haskell/unix/pull/202</a></li>
<li><a href="https://github.com/haskell/directory/pull/136">https://github.com/haskell/directory/pull/136</a></li>
</ul></li>
<li>Haskell Foundation thread: <a href="https://github.com/haskellfoundation/tech-proposals/issues/35">https://github.com/haskellfoundation/tech-proposals/issues/35</a></li>
<li>Reddit discussion: <a href="https://www.reddit.com/r/haskell/comments/vivjdo/abstract_filepath_coming_soon/">https://www.reddit.com/r/haskell/comments/vivjdo/abstract_filepath_coming_soon/</a></li>
</ol>
<h2 id="contributors">Contributors</h2>
<ol type="1">
<li>Author, filepath maintainer and proposal champion: Julian Ospald (me)</li>
<li>Bodigrim providing help and support as CLC chair, giving reviews as bytestring maintainer and providing help with questions about encoding</li>
<li><code>bytestring</code> maintainers providing review for the <code>ShortByteString</code> PR</li>
<li><code>unix</code> maintainers providing PR review</li>
<li>Tamar Christina (<code>Win32</code> maintainer) providing PR review and further guidance for the <code>file-io</code> library</li>
<li><code>directory</code> maintainer providing PR review</li>
<li>Ericson2314 via various dicussions</li>
<li>Koz Ross helping with encoding questions</li>
<li>GHC team helping with getting this into 9.6</li>
<li>HF encouraging me</li>
<li>reddit community giving loads of opinions on function names ;)</li>
<li>various people on IRC discussing alternatives like PEP-383/UTF-8b/WTF-8</li>
</ol>
<h2 id="patch-load">Patch load</h2>
<ul>
<li>filepath: 11126 insertions(+), 3062 deletions(-)</li>
<li>bytestring: 1795 insertions(+), 145 deletions(-)</li>
<li>Win32: 2668 insertions(+), 986 deletions(-)</li>
<li>unix: 8705 insertions(+), 3 deletions(-)</li>
<li>directory: 2959 insertions(+), 939 deletions(-)</li>
<li>file-io: 296 insertions(+)</li>
</ul>
<p>Total: 27549 insertions(+), 5135 deletions(-)</p>
<h2 id="how-to-help">How to help</h2>
<ul>
<li>create issues for your favorite libraries to support <code>OsPath</code> linking to this blog</li>
<li>create PRs for existing issues:
<ul>
<li><a href="https://github.com/haskell/process/issues/252">https://github.com/haskell/process/issues/252</a></li>
</ul></li>
</ul>
<h2 id="faq">FAQ</h2>
<h3 id="why-is-there-no-isstring-instance-overloadedstrings">Why is there no IsString instance (OverloadedStrings)?</h3>
<p><code>IsString</code> has a broken API: <a href="https://github.com/haskell/bytestring/issues/140">https://github.com/haskell/bytestring/issues/140</a></p>
<p>It can’t express failure. Conversion to <code>OsPath</code> can fail. Use the provided QuasiQuoters instead.</p>
<h3 id="why-is-this-not-in-base">Why is this not in base?</h3>
<p>Nothing is stopping this from eventually getting into base. But the barrier of doing so is much higher. It may happen eventually.</p>
<h3 id="when-will-filepath-be-dropped">When will ‘FilePath’ be dropped?</h3>
<p>Probably never. It would break loads of code. We don’t want to do that, for now.</p>
<h3 id="yet-another-string-type">Yet another String type?</h3>
<p>Right… I suggest using python if you don’t like types ;)</p>]]></summary>
</entry>
<entry>
    <title>From conduit to streamly</title>
    <link href="https://github.com/hasufell/hasufell.github.io/posts/2021-10-22-conduit-to-streamly.html" />
    <id>https://github.com/hasufell/hasufell.github.io/posts/2021-10-22-conduit-to-streamly.html</id>
    <published>2021-10-22T00:00:00Z</published>
    <updated>2021-10-22T00:00:00Z</updated>
    <summary type="html"><![CDATA[<h2 id="motivation">Motivation</h2>
<p>At GHCup I recently put a lot of effort into <a href="https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/212">reducing the dependency footprint</a> to improve build times. Since <code>conduit</code> was not a direct dependency and only used for yaml parsing and some other things, I replaced those deps with alternatives or re-implemented them (like logging).</p>
<p><a href="https://hackage.haskell.org/package/yaml">yaml</a>, which uses conduit under the hood, was replaced with <a href="https://hackage.haskell.org/package/HsYAML">HsYAML</a>, but to my despair… that turned out to be <a href="https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/270">10 times slower</a>, which also caused <a href="https://github.com/haskell-hvr/HsYAML/issues/40">issues for pandoc</a>.</p>
<p><a href="https://hackage.haskell.org/package/conduit">Conduit</a> is an excellent fully featured streaming library, but I didn’t want to go back to it by re-introducing yaml, since GHCup previously depended on <code>streamly</code> and will likely do so in the future. So I simply decided to migrate yaml to streamly: <a href="https://hackage.haskell.org/package/yaml-streamly">https://hackage.haskell.org/package/yaml-streamly</a>.</p>
<p><a href="https://github.com/composewell/streamly">Streamly</a> is a very general streaming library with a the strong focus on <a href="https://github.com/composewell/streaming-benchmarks#streamly-vs-conduit">performance</a> through inlining and stream fusion optimizations. As such, it may exceed other implementations performance, but also depends quite heavily on GHC behavior, flags, INLINE pragmas etc. It can also be used as an <a href="https://github.com/composewell/streamly/blob/master/docs/streamly-vs-async.md">alternative for async</a>, for reactive programming and much more.</p>
<p>So in this post, I will shortly explain conduit and streamly and provide a simple migration guide.</p>
<h2 id="recap-on-conduit">Recap on conduit</h2>
<p>There are many approaches on streaming. Conduit and streamly diverge quite heavily in terms of paradigm and API.</p>
<p>Conduit expresses streaming by providing a type that captures <code>i</code>nput, <code>o</code>utput and a possible final <code>r</code>esult, all in one type (and the obligatory effect <code>m</code>):</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="kw">data</span> <span class="dt">ConduitT</span> i o m r</span></code></pre></div>
<p>As such, it expresses:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/conduit-1.3.4.2/docs/Data-Conduit-Combinators.html#g:1">Producers</a></li>
<li><a href="https://hackage.haskell.org/package/conduit-1.3.4.2/docs/Data-Conduit-Combinators.html#g:10">Transformers</a></li>
<li><a href="https://hackage.haskell.org/package/conduit-1.3.4.2/docs/Data-Conduit-Combinators.html#g:6">Consumers</a></li>
</ul>
<h3 id="producers">Producers</h3>
<p>These are generators from a seed value. Conduit defines it generically as such:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true"></a><span class="ot">unfold ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> (b <span class="ot">-&gt;</span> <span class="dt">Maybe</span> (a, b)) <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> <span class="dt">ConduitT</span> i a m ()</span></code></pre></div>
<p>A simple unfold that lets us turn a list into a stream would be:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true"></a><span class="co">-- this is also provided by conduit</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true"></a><span class="ot">sourceList ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> [a] <span class="ot">-&gt;</span> <span class="dt">ConduitT</span> i a m ()</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true"></a>sourceList <span class="ot">=</span> unfold gen</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true"></a> <span class="kw">where</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true"></a><span class="ot">  gen ::</span> [a] <span class="ot">-&gt;</span> <span class="dt">Maybe</span> (a, [a])</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true"></a>  gen (x<span class="op">:</span>xs) <span class="ot">=</span> <span class="dt">Just</span> (x,xs)</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true"></a>  gen _      <span class="ot">=</span> <span class="dt">Nothing</span> <span class="co">-- stream aborts</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true"></a><span class="co">-- our own stream of &quot;output&quot; chars with no final result</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true"></a><span class="ot">chars ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">ConduitT</span> i <span class="dt">Char</span> m ()</span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true"></a>chars <span class="ot">=</span> sourceList <span class="st">&quot;abc&quot;</span></span></code></pre></div>
<p>As can be seen, the <code>o</code> in <code>data ConduitT i o m r</code> gets fixed to <code>Char</code>. A Producer can then be be “piped” into another conduit, e.g. a transformer.</p>
<p>A producer focuses on the <code>o</code>utput.</p>
<h3 id="transformer">Transformer</h3>
<p>A transformer is like <code>map :: (a -&gt; b) -&gt; [a] -&gt; [b]</code>. It transforms the stream and may yield a different type.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true"></a><span class="co">-- provided by conduit, notice how it has only one argument</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true"></a><span class="fu">map</span><span class="ot"> ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> (a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> <span class="dt">ConduitT</span> a b m ()</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true"></a><span class="co">-- transforms Char to Int</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true"></a><span class="ot">charToInt ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">ConduitT</span> <span class="dt">Char</span> <span class="dt">Int</span> m ()</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true"></a>charToInt <span class="ot">=</span> <span class="fu">map</span> <span class="fu">ord</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true"></a></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true"></a><span class="co">-- applies the transformation to the chars, yielding a Producer</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true"></a><span class="co">-- we&#39;ll explaing &#39;.|&#39; shortly</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true"></a><span class="ot">ints ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">ConduitM</span> a <span class="dt">Int</span> m ()</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true"></a>ints <span class="ot">=</span> chars <span class="op">.|</span> charToInt</span></code></pre></div>
<p>Notable is also that the Functor <code>fmap</code> isn’t a transformation. It would map on the final value, not the produced values. That’s why we need <code>Data.Conduit.List.map</code>. Streamly is very different here.</p>
<p>A transformer maps the <code>i</code>nput to the <code>o</code>utput.</p>
<p>To apply a transformation, we use the <code>(.|)</code> pipe operator, which reminds us of shell pipes:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true"></a><span class="ot">(.|) ::</span> <span class="dt">Monad</span> m</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true"></a>     <span class="ot">=&gt;</span> <span class="dt">ConduitM</span> a b m () <span class="co">-- ^ producer of values &#39;b&#39;</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true"></a>     <span class="ot">-&gt;</span> <span class="dt">ConduitM</span> b c m r  <span class="co">-- ^ transformer (b -&gt; c), or consumer</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true"></a>     <span class="ot">-&gt;</span> <span class="dt">ConduitM</span> a c m r</span></code></pre></div>
<p>It takes a little while to see what’s going on. The type variables guide us.</p>
<h3 id="consumer">Consumer</h3>
<p>A consumer works on the input stream, much like a transformer, but may also yield a final result. E.g. If we wanted to return all the Int’s we just converted from the Char stream, we’d do:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true"></a><span class="co">-- provided by conduit</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true"></a><span class="fu">foldl</span><span class="ot"> ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> (a <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> a) <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> <span class="dt">ConduitT</span> b o m a</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true"></a></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true"></a><span class="co">-- &#39;a&#39; (the input) gets folded as a list, so the final result is &#39;[a]&#39;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true"></a><span class="ot">toList ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">ConduitT</span> a o m [a]</span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true"></a>toList <span class="ot">=</span> <span class="fu">foldl</span> (\a b <span class="ot">-&gt;</span> b<span class="op">:</span>a) []</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true"></a></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true"></a><span class="co">-- applying the fold on the stream of Ints</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true"></a><span class="ot">foldedInts ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">ConduitM</span> a c m [<span class="dt">Int</span>]</span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true"></a>foldedInts <span class="ot">=</span> ints <span class="op">.|</span> toList</span></code></pre></div>
<p>The consumer focuses on the <code>i</code>nput to produce a final <code>r</code>esult (however, consumers may also drop elements from the stream).</p>
<p>As demonstrated, one has to look closely at the type parameters in <code>data ConduitT i o m r</code> to understand a conduit.</p>
<p>All concepts are unified in one type. Most operations need specific combinators.</p>
<h3 id="wrapping-up-conduit">Wrapping up conduit</h3>
<p>Finally, we can get our Ints:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true"></a><span class="ot">ints ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> m [<span class="dt">Int</span>]</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true"></a>ints <span class="ot">=</span> runConduit foldedInts</span></code></pre></div>
<p>That’s basically conduit. A conduit as such doesn’t really express streams. Instead we’re dealing with stream processors (functions).</p>
<h2 id="streamly">Streamly</h2>
<p>Streamly’s approach is very different. It focuses on the simple concept of a <em>stream</em> of elements. It has 4 main types:</p>
<ul>
<li><a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Prelude.html#g:50">streams</a>: <code>(Monad m, IsStream t) =&gt; t m a</code></li>
<li><a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Data-Unfold.html">unfolds</a>: <code>data Unfold m a b</code></li>
<li><a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Data-Fold.html">folds</a>: <code>data Fold m a b</code></li>
<li><a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Internal-Data-Parser.html">parsers</a>: <code>newtype Parser m a b</code></li>
</ul>
<p>As can be seen, this is nothing like <code>data ConduitT i o m r</code>. I also note that <code>IsStream t</code> is abstract to allow for different types of streams like <code>SerialT</code> or <code>AsyncT</code>, which I won’t go into detail about here.</p>
<p>We’ll now figure out how these concepts translate to conduit.</p>
<h3 id="producers-1">Producers</h3>
<p>Conduits producers are basically <em>Unfolds</em>.</p>
<p>The simplest function to create an <code>Unfold</code> is:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true"></a><span class="ot">unfoldr ::</span> <span class="dt">Applicative</span> m <span class="ot">=&gt;</span> (a <span class="ot">-&gt;</span> <span class="dt">Maybe</span> (b, a)) <span class="ot">-&gt;</span> <span class="dt">Unfold</span> m a b</span></code></pre></div>
<p>…which actually looks a lot like conduit:</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true"></a><span class="ot">unfold ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> (b <span class="ot">-&gt;</span> <span class="dt">Maybe</span> (a, b)) <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> <span class="dt">ConduitT</span> i a m ()</span></code></pre></div>
<p>The difference in streamly is that we provide the initial seed value when we turn the Unfold into a Stream.</p>
<p>So, let’s do the same procedure as above. We’ll create a list of Chars:</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true"></a><span class="co">-- equivalent to conduits &#39;sourceList&#39;, also provided by streamly</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true"></a><span class="ot">fromList ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">Unfold</span> m [a] a</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true"></a>fromList <span class="ot">=</span> unfoldr gen</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true"></a> <span class="kw">where</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true"></a><span class="ot">  gen ::</span> [a] <span class="ot">-&gt;</span> <span class="dt">Maybe</span> (a, [a])</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true"></a>  gen (x<span class="op">:</span>xs) <span class="ot">=</span> <span class="dt">Just</span> (x,xs)</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true"></a>  gen _      <span class="ot">=</span> <span class="dt">Nothing</span> <span class="co">-- stream aborts</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true"></a></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true"></a><span class="co">-- provided by streamly</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true"></a><span class="co">-- given a seed value, turn an Unfold into a stream</span></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true"></a><span class="ot">unfold ::</span> (<span class="dt">IsStream</span> t, <span class="dt">Monad</span> m) <span class="ot">=&gt;</span> <span class="dt">Unfold</span> m a b <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> t m b</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true"></a></span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true"></a><span class="co">-- we turn the unfold into a stream of chars</span></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true"></a><span class="ot">chars ::</span> (<span class="dt">IsStream</span> t, <span class="dt">Monad</span> m) <span class="ot">=&gt;</span> t m <span class="dt">Char</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true"></a>chars <span class="ot">=</span> Streamly.Prelude.unfold fromList <span class="st">&quot;abc&quot;</span></span></code></pre></div>
<p>This type <code>t m Char</code> looks a lot simpler. It’s basically a glorified list with possible effects run for every element.</p>
<h3 id="transformers">Transformers</h3>
<p>A transformer doesn’t have its own type. It’s in my opinion much simpler than conduit. Here, we can simply reuse the Prelude’s <code>fmap</code>. The main difference is that we have an input and an output stream, so:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true"></a><span class="co">-- transforms Char to Int</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true"></a><span class="ot">charToInt ::</span> (<span class="dt">IsStream</span> t, <span class="dt">Monad</span> m, <span class="dt">Functor</span> (t m)) <span class="ot">=&gt;</span> t m <span class="dt">Char</span> <span class="ot">-&gt;</span> t m <span class="dt">Int</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true"></a>charToInt inputStream <span class="ot">=</span> <span class="fu">fmap</span> <span class="fu">ord</span> inputStream</span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true"></a></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true"></a><span class="co">-- applies the transformation to the chars, yielding a stream of Ints</span></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true"></a><span class="ot">ints ::</span> (<span class="dt">IsStream</span> t, <span class="dt">Monad</span> m, <span class="dt">Functor</span> (t m)) <span class="ot">=&gt;</span> t m <span class="dt">Int</span></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true"></a>ints <span class="ot">=</span> charToInt chars</span></code></pre></div>
<p>This feels much more like lists! Compare with <code>fmap ord "abc"</code>. Streams can be passed around and transformed just like lists. If you want to run effects for every item, you just use the Monad interface:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true"></a><span class="ot">charToInt ::</span> (<span class="dt">IsStream</span> t, <span class="dt">Monad</span> m, <span class="dt">Monad</span> (t m)) <span class="ot">=&gt;</span> t m <span class="dt">Char</span> <span class="ot">-&gt;</span> t m <span class="dt">Int</span></span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true"></a>charToInt inputStream <span class="ot">=</span> inputStream <span class="op">&gt;&gt;=</span> <span class="fu">pure</span> <span class="op">.</span> <span class="fu">ord</span></span></code></pre></div>
<p>However, this creates a data dependency (as we’re used from Monad). There’s the more general <code>mapM</code> that can run effects in parallel:</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true"></a><span class="fu">mapM</span><span class="ot"> ::</span> (<span class="dt">IsStream</span> t, <span class="dt">MonadAsync</span> m) <span class="ot">=&gt;</span> (a <span class="ot">-&gt;</span> m b) <span class="ot">-&gt;</span> t m a <span class="ot">-&gt;</span> t m b</span></code></pre></div>
<p>Excellent. So Functor, Monad etc. follow our intuition.</p>
<h3 id="consumers">Consumers</h3>
<p>Simple consumers in streamly terms are usually <strong>Folds</strong>.</p>
<p>E.g. if we wanted to convert our stream of Ints to an actual list of Ints we would combine our input stream with a Fold.</p>
<p>Remember the Fold type <code>data Fold m a b</code>, where <code>a</code> are the values of the input stream and <code>b</code> is the final folded value.</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true"></a><span class="co">-- provided by streamly for creating a Fold</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true"></a><span class="ot">foldl&#39; ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> (b <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> <span class="dt">Fold</span> m a b</span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true"></a></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true"></a><span class="co">-- provided by streamly for executing a fold over a stream</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true"></a><span class="ot">fold ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">Fold</span> m a b <span class="ot">-&gt;</span> <span class="dt">Stream</span> m a <span class="ot">-&gt;</span> m b</span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true"></a></span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true"></a><span class="co">-- A Fold that turns any input stream into a list</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true"></a><span class="ot">toList ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">Fold</span> m a [a]</span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true"></a>toList <span class="ot">=</span> foldl&#39; (\a b <span class="ot">-&gt;</span> b<span class="op">:</span>a) []</span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true"></a></span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true"></a><span class="co">-- Applying the Fold to an actual stream already executes it</span></span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true"></a><span class="ot">foldedInts ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> m [<span class="dt">Int</span>]</span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true"></a>foldedInts <span class="ot">=</span> fold toList ints</span></code></pre></div>
<h3 id="parsers">Parsers</h3>
<p>Folds don’t have a monadic interface (yet). If we want backtracking and a monadic interface to choose the next step depending on the current element in the stream, we can use a <a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Internal-Data-Parser.html">Parser</a>.</p>
<p>In conduit, we can use consumers like <a href="https://hackage.haskell.org/package/conduit-1.3.4.2/docs/Data-Conduit-Combinators.html#v:head">head</a> and <a href="https://hackage.haskell.org/package/conduit-1.3.4.2/docs/Data-Conduit-Combinators.html#v:peek">peek</a> and utilize the Monad interface of <code>ConduitT</code> to make our decisions. Theoretically, we could do the same in the Stream type of streamly via <a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Prelude.html#v:uncons">uncons</a>, but the parser feels more idiomatic here.</p>
<p>I note that there is a parser-like package <a href="https://hackage.haskell.org/package/conduit-parse">conduit-parse</a>, but the <a href="https://github.com/snoyberg/yaml/blob/c4392f3855002ab0bbf9bc16e1e32034254234a7/yaml/src/Data/Yaml/Internal.hs#L286">yaml conduit code</a> doesn’t utilize that and this blog was written while I converted yaml to streamly.</p>
<p>The streamly parser type is the same as a Fold: <code>newtype Parser m a b</code>.</p>
<p>It parses a streamed value <code>a</code> into <code>b</code>. Much of the <a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Internal-Data-Parser.html#g:2">API</a> resembles what you’re used to of <code>parsec</code> or <code>attoparsec</code> etc.</p>
<p>Let’s look at this conduit code (not tested to compile):</p>
<div class="sourceCode" id="cb15"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true"></a><span class="kw">import</span> <span class="kw">qualified</span> <span class="dt">Data.Conduit.Combinators</span> <span class="kw">as</span> <span class="dt">C</span></span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true"></a></span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true"></a><span class="ot">chars ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">ConduitT</span> i <span class="dt">Char</span> m ()</span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true"></a>chars <span class="ot">=</span> sourceList <span class="st">&quot;a1b2c3&quot;</span></span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true"></a></span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true"></a><span class="co">-- We parse &#39;1&#39; from &#39;a1&#39;, &#39;2&#39; from &#39;b2&#39; and so on, no matter</span></span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true"></a><span class="co">-- the order the pairs appear in.</span></span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true"></a><span class="ot">parse&#39; ::</span> <span class="dt">MonadIO</span> m <span class="ot">=&gt;</span> <span class="dt">ConduitT</span> <span class="dt">Char</span> o m [<span class="dt">Int</span>]</span>
<span id="cb15-9"><a href="#cb15-9" aria-hidden="true"></a>parse&#39; <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb15-10"><a href="#cb15-10" aria-hidden="true"></a>  mc <span class="ot">&lt;-</span> C.head</span>
<span id="cb15-11"><a href="#cb15-11" aria-hidden="true"></a>  <span class="kw">case</span> mc <span class="kw">of</span></span>
<span id="cb15-12"><a href="#cb15-12" aria-hidden="true"></a>    <span class="dt">Just</span> <span class="ch">&#39;a&#39;</span> <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb15-13"><a href="#cb15-13" aria-hidden="true"></a>       mcn <span class="ot">&lt;-</span> C.head</span>
<span id="cb15-14"><a href="#cb15-14" aria-hidden="true"></a>       <span class="kw">case</span> mcn <span class="kw">of</span></span>
<span id="cb15-15"><a href="#cb15-15" aria-hidden="true"></a>         <span class="dt">Just</span> <span class="ch">&#39;1&#39;</span> <span class="ot">-&gt;</span> (<span class="dv">1</span><span class="op">:</span>) <span class="op">&lt;$&gt;</span> parse&#39;</span>
<span id="cb15-16"><a href="#cb15-16" aria-hidden="true"></a>         <span class="dt">Just</span> cn  <span class="ot">-&gt;</span> liftIO <span class="op">$</span> throwIO <span class="op">$</span> <span class="st">&quot;Unexpected char: &quot;</span> <span class="op">++</span> [cn]</span>
<span id="cb15-17"><a href="#cb15-17" aria-hidden="true"></a>         <span class="dt">Nothing</span>  <span class="ot">-&gt;</span> <span class="fu">pure</span> []</span>
<span id="cb15-18"><a href="#cb15-18" aria-hidden="true"></a>    <span class="dt">Just</span> <span class="op">...</span> <span class="co">-- and so on</span></span>
<span id="cb15-19"><a href="#cb15-19" aria-hidden="true"></a>    <span class="dt">Nothing</span> <span class="ot">-&gt;</span> <span class="fu">pure</span> []</span></code></pre></div>
<p>To translate this to streamly, we would write:</p>
<div class="sourceCode" id="cb16"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true"></a><span class="ot">chars ::</span> (<span class="dt">IsStream</span> t, <span class="dt">Monad</span> m) <span class="ot">=&gt;</span> t m <span class="dt">Char</span></span>
<span id="cb16-2"><a href="#cb16-2" aria-hidden="true"></a>chars <span class="ot">=</span> Streamly.Prelude.unfold fromList <span class="st">&quot;a1b2c3&quot;</span></span>
<span id="cb16-3"><a href="#cb16-3" aria-hidden="true"></a></span>
<span id="cb16-4"><a href="#cb16-4" aria-hidden="true"></a><span class="co">-- we define a helper that acts like conduits C.head</span></span>
<span id="cb16-5"><a href="#cb16-5" aria-hidden="true"></a><span class="ot">anyChar ::</span> <span class="dt">MonadCatch</span> m <span class="ot">=&gt;</span> <span class="dt">Parser</span> m <span class="dt">Char</span> (<span class="dt">Maybe</span> <span class="dt">Char</span>)</span>
<span id="cb16-6"><a href="#cb16-6" aria-hidden="true"></a>anyChar <span class="ot">=</span> (<span class="dt">Just</span> <span class="op">&lt;$&gt;</span> satisfy (<span class="fu">const</span> <span class="dt">True</span>)) <span class="op">&lt;|&gt;</span> <span class="fu">pure</span> <span class="dt">Nothing</span></span>
<span id="cb16-7"><a href="#cb16-7" aria-hidden="true"></a></span>
<span id="cb16-8"><a href="#cb16-8" aria-hidden="true"></a><span class="co">-- We parse &#39;1&#39; from &#39;a1&#39;, &#39;2&#39; from &#39;b2&#39; and so on, no matter</span></span>
<span id="cb16-9"><a href="#cb16-9" aria-hidden="true"></a><span class="co">-- the order the pairs appear in.</span></span>
<span id="cb16-10"><a href="#cb16-10" aria-hidden="true"></a><span class="ot">parse&#39; ::</span> <span class="dt">MonadIO</span> m <span class="ot">=&gt;</span> <span class="dt">Parser</span> m <span class="dt">Char</span> [<span class="dt">Int</span>]</span>
<span id="cb16-11"><a href="#cb16-11" aria-hidden="true"></a>parse&#39; <span class="ot">=</span> <span class="kw">do</span></span>
<span id="cb16-12"><a href="#cb16-12" aria-hidden="true"></a>  mc <span class="ot">&lt;-</span> anyChar</span>
<span id="cb16-13"><a href="#cb16-13" aria-hidden="true"></a>  <span class="kw">case</span> mc <span class="kw">of</span></span>
<span id="cb16-14"><a href="#cb16-14" aria-hidden="true"></a>    <span class="dt">Just</span> <span class="ch">&#39;a&#39;</span> <span class="ot">-&gt;</span> <span class="kw">do</span></span>
<span id="cb16-15"><a href="#cb16-15" aria-hidden="true"></a>       mcn <span class="ot">&lt;-</span> anyChar</span>
<span id="cb16-16"><a href="#cb16-16" aria-hidden="true"></a>       <span class="kw">case</span> mcn <span class="kw">of</span></span>
<span id="cb16-17"><a href="#cb16-17" aria-hidden="true"></a>         <span class="dt">Just</span> <span class="ch">&#39;1&#39;</span> <span class="ot">-&gt;</span> (<span class="dv">1</span><span class="op">:</span>) <span class="op">&lt;$&gt;</span> parse&#39;</span>
<span id="cb16-18"><a href="#cb16-18" aria-hidden="true"></a>         <span class="dt">Just</span> cn  <span class="ot">-&gt;</span> liftIO <span class="op">$</span> throwIO <span class="op">$</span> <span class="st">&quot;Unexpected char: &quot;</span> <span class="op">++</span> [cn]</span>
<span id="cb16-19"><a href="#cb16-19" aria-hidden="true"></a>         <span class="dt">Nothing</span>  <span class="ot">-&gt;</span> <span class="fu">pure</span> []</span>
<span id="cb16-20"><a href="#cb16-20" aria-hidden="true"></a>    <span class="dt">Just</span> <span class="op">...</span> <span class="co">-- and so on</span></span>
<span id="cb16-21"><a href="#cb16-21" aria-hidden="true"></a>    <span class="dt">Nothing</span> <span class="ot">-&gt;</span> <span class="fu">pure</span> []</span></code></pre></div>
<p>This looks <em>exactly</em> like the conduit code, except we replaced <code>head</code> with <code>anyChar</code>. Although we could likely reduce it further instead of pattern matching on the chars.</p>
<p>Running a parser is like running a fold. We need an input stream:</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true"></a><span class="ot">parse ::</span> <span class="dt">MonadThrow</span> m <span class="ot">=&gt;</span> <span class="dt">Parser</span> m a b <span class="ot">-&gt;</span> <span class="dt">SerialT</span> m a <span class="ot">-&gt;</span> m b</span></code></pre></div>
<h3 id="wrapping-up-streamly">Wrapping up streamly</h3>
<p>Running a stream is usually done by applying a <code>Fold</code>, as we’ve done above. We can also turn a stream into a list directly:</p>
<div class="sourceCode" id="cb18"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true"></a><span class="ot">toList ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">SerialT</span> m a <span class="ot">-&gt;</span> m [a]</span></code></pre></div>
<p>Or just evaluate the stream and discard the values:</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode hs"><code class="sourceCode haskell"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true"></a><span class="ot">drain ::</span> <span class="dt">Monad</span> m <span class="ot">=&gt;</span> <span class="dt">SerialT</span> m a <span class="ot">-&gt;</span> m ()</span></code></pre></div>
<p>All these functions also exist as Folds, so these are just convenience wrappers.</p>
<p>As can be seen, streamly isn’t based on stream processors like conduit. Instead it composes stream data directly and behaves pretty much like lists. Usually we don’t need special operators. Functor, Monad etc. follow our intuition from lists.</p>
<p>We’ve also seen that there’s an abstract <code>IsStream</code> class and specific streaming types like <code>SerialT</code> (for serially processed streams), <code>AsyncT</code> (for concurrent streams) and so on. These are explained in more detail in the streamly documentation.</p>
<h2 id="back-to-yaml">Back to yaml</h2>
<p>So how does this translate to yaml parsing? Well, the <code>yaml</code> package uses the <a href="https://github.com/yaml/libyaml">libyaml C library</a> for parsing, which is an event driven parser. So we get a <a href="https://github.com/snoyberg/yaml/blob/c4392f3855002ab0bbf9bc16e1e32034254234a7/libyaml/src/Text/Libyaml.hs#L577">stream of events</a> and then <a href="https://github.com/snoyberg/yaml/blob/c4392f3855002ab0bbf9bc16e1e32034254234a7/yaml/src/Data/Yaml/Internal.hs#L187">turn that into a single JSON value</a> and then let <code>aeson</code> do its magic.</p>
<p>Finally, for reference, here’s the migration patch: <a href="https://github.com/hasufell/streamly-yaml/commit/bfd1da498588af906cbc5d3bb519f1ccdf7ad63e">https://github.com/hasufell/streamly-yaml/commit/bfd1da498588af906cbc5d3bb519f1ccdf7ad63e</a></p>
<p>In fact, it didn’t require a rewrite at all. Simply applying the concepts from above was enough. Figuring out that we need a Parser type etc. took a while (I tried with Fold first). Thanks to the helpful streamly developers for providing guidance. There were some rough edges here and there, since much of the streamly API is still marked as <strong>Internal</strong>.</p>
<h3 id="performance">Performance</h3>
<p>Did it actually improve performance?</p>
<p>On my first attempt, I used the wrong inefficient internal <a href="https://hackage.haskell.org/package/streamly-0.8.0/docs/Streamly-Internal-Data-Parser-ParserD.html#t:Parser">ParserD</a> type, which seemed to cause exponential allocations. After fixing that, I was still slower than conduit. Since streamly heavily relies on GHCs inliner, this wasn’t a surprise. It required <a href="https://github.com/hasufell/streamly-yaml/commit/640596f344675cc21970a66335b5f1ca2ae4c3c9">some effort</a>, but finally the performance was on-par with conduit (tested informally via the <code>yaml2json</code> executable on a 100mb YAML file).</p>
<p>Streamly also provides <a href="https://github.com/composewell/streamly/blob/master/docs/optimizing.md">some</a> <a href="https://github.com/composewell/streamly/blob/master/docs/building.md#compilation-options">guidance</a> for optimization.</p>
<p>I guess since the actual parsing is done by the C code and the <code>event-&gt;json</code> conversion is really a slow element-by-element monadic parsing transformation, there’s not much space to improve performance anyway.</p>
<p>If you find ideas about how to improve it further, please let me know.</p>
<h3 id="dependency-footprint">Dependency footprint</h3>
<p>Did this actually reduce dependency footprint?</p>
<p>Well, no. But the point was to only depend on a single streaming framework. I also note that streamly is <a href="https://github.com/composewell/streamly/issues/533">planning to split up the <code>streamly</code> package</a> into <code>streamly-core</code> (only depends on boot packages) and separate out further feature-packages.</p>
<h2 id="conclusion">Conclusion</h2>
<ol type="1">
<li>migrating conduit code to streamly is easier than I thought</li>
<li>performance optimization in streamly requires some time and effort</li>
<li>you definitely want performance regression tests with streamly to ensure new GHC versions or refactorings don’t cause regressions</li>
</ol>
<h2 id="whats-next">What’s next?</h2>
<p>Writing a streamly yaml parser in pure Haskell?</p>
<h2 id="discussion">Discussion</h2>
<ul>
<li><a href="https://discourse.haskell.org/t/blog-from-conduit-to-streamly/3492/4">Discourse</a></li>
<li><a href="https://www.reddit.com/r/haskell/comments/qe72nv/from_conduit_to_streamly/">Reddit</a></li>
</ul>]]></summary>
</entry>

</feed>
