<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>The Haskell Programming Language&#x27;s blog</title>
    <link rel="self" type="application/atom+xml" href="https://blog.haskell.org/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://blog.haskell.org"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-08-04T00:00:00+00:00</updated>
    <id>https://blog.haskell.org/atom.xml</id>
    <entry xml:lang="en">
        <title>A Revised Haskell 2010 Language Report</title>
        <published>2026-08-04T00:00:00+00:00</published>
        <updated>2026-08-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/revised-haskell-2010-report/"/>
        <id>https://blog.haskell.org/revised-haskell-2010-report/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/revised-haskell-2010-report/">&lt;p&gt;The Haskell 2010 language report was released almost exactly 16 years ago, and although various attempts have been made, no further report has been released.
This does not mean, however, that the language hasn&#x27;t changed.
On the contrary, the Haskell that we write today is quite different to the one we wrote 16 years ago, and the language has become more beautiful, consistent and ergonomic in the process.
The specification, unfortunately, couldn&#x27;t keep up.&lt;&#x2F;p&gt;
&lt;p&gt;Many members of our community express some pessimism when it comes to the possibility of a new report. Sure, they would also love to have a new and updated report, but they think that it is either too hard or not worth the effort. I think that is not true. Having an up-to-date report is essential for the health of our community, and it is actually possible for us to get there in a reasonable amount of time with a reasonable amount of work.
Here is my plan for how we can get there, and how we can finish the process before the new year.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-we-need-a-new-report&quot;&gt;Why We Need a New Report&lt;&#x2F;h2&gt;
&lt;p&gt;The Haskell Language Report is an essential part of the documentation of the language. It is not the first thing that a beginner should consult when they learn Haskell, but it is an important resource for those who want to advance to being an intermediate or experienced Haskeller. If you want to learn about the precise syntax of floating point literals, the rules and desugaring for do-notation, or the meaning of &lt;code&gt;default&lt;&#x2F;code&gt; declarations, the report is the authorative resource where you can find answers.
It is therefore unfortunate if the answers that you find in the report are wrong, or do not correspond to what compilers implement or the base library provides.
In that case, you have to retrace the proposals and discussions that lead to the current state:
The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;wiki.haskell.org&#x2F;Functor-Applicative-Monad_Proposal&quot;&gt;Functor-Applicative-Monad&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.haskell.org&#x2F;haskell&#x2F;prime&#x2F;-&#x2F;wikis&#x2F;libraries&#x2F;proposals&#x2F;monad-fail&quot;&gt;MonadFail&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;wiki.haskell.org&#x2F;Foldable_Traversable_In_Prelude&quot;&gt;Foldable-Traversable&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.haskell.org&#x2F;ghc&#x2F;ghc&#x2F;-&#x2F;wikis&#x2F;proposal&#x2F;monad-of-no-return&quot;&gt;Monad-of-no-return&lt;&#x2F;a&gt; proposals undoubtedly made the language better, but they also turn programmers looking for answers in the report into involuntary Haskell historians.&lt;&#x2F;p&gt;
&lt;p&gt;If the language report were the only Haskell resource available to you, then any non-trivial Haskell 2010 program that you would write is almost guaranteed to be incompatible with what modern compilers like GHC or MicroHS expect. We can, and should, fix that.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-to-get-the-cost-benefit-tradeoff-right&quot;&gt;How to Get the Cost-Benefit Tradeoff Right&lt;&#x2F;h2&gt;
&lt;p&gt;Writing a new report is a lot of work, and we want to avoid running into the problems that stalled previous attempts of writing one.
It is therefore important that we set ourselves a goal that is both achievable and provides a tangible benefit for the Haskell community.
The most obvious such goal is to start with a &lt;strong&gt;Revised Haskell 2010 Language Report&lt;&#x2F;strong&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A revised report is eminently achievable: We have a very clear &quot;todo list&quot; in the documented discrepancies in the user guide (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;ghc&#x2F;latest&#x2F;docs&#x2F;users_guide&#x2F;bugs.html&quot;&gt;GHC User Guide: Bugs and Infelicities&lt;&#x2F;a&gt;). Most of these listed discrepancies are already well-documented in the accepted proposals that lead to them.&lt;&#x2F;li&gt;
&lt;li&gt;None of the changes that we have to implement are likely to be contentious: They reflect the Haskell that we are already writing today. Since the required changes are not contentious, we can use a lightweight decision process in the compilation of the revised report.&lt;&#x2F;li&gt;
&lt;li&gt;There is a clear benefit for the community: The most important piece of our reference documentation is no longer outdated. We can refer people to the Haskell report without having to warn them to stay clear of parts of the report that no longer hold true.&lt;&#x2F;li&gt;
&lt;li&gt;Since we don&#x27;t define a new Haskell version with a new feature set, but only a revised version of a previous report, we don&#x27;t generate any churn for the ecosystem or compiler writers. Taking inspiration from Tolkien who rewrote the Hobbit after he came up with the story of the Lord of the Rings, we can &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Retroactive_continuity&quot;&gt;retcon&lt;&#x2F;a&gt; the report and pretend that the revised report is what we meant by &lt;code&gt;Haskell2010&lt;&#x2F;code&gt; all along. We can then simplify the GHC user guide and remove most of its section 16 which documents infelicities compared to the existing report.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So here is the a target that we can set for ourselves: Every program that is valid according to the revised Haskell 2010 language report will be accepted by MicroHS and GHC using the &lt;code&gt;Haskell2010&lt;&#x2F;code&gt; language edition.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;let-s-talk-technicalities&quot;&gt;Let&#x27;s Talk Technicalities&lt;&#x2F;h2&gt;
&lt;p&gt;With the motivation and goal out of the way, let us discuss some technical details when it comes to working on the report, building it, and generating the final PDF and HTML documents.&lt;&#x2F;p&gt;
&lt;p&gt;The Haskell 2010 language report is available both as a PDF and HTML document, and there are no obvious good reasons to change this.
Working on a document of the complexity and length of the Haskell report can be made joyful if the authoring technology is right, and arduous if we are struggling with long compile times (Compiling long-ish PDFs with LaTeX can take minutes using the wrong tools and packages!), inscrutable error messages or a difficult scripting and programming model.&lt;&#x2F;p&gt;
&lt;p&gt;The previous version of the Haskell report was written using a combination of command line tools (&lt;code&gt;sed&lt;&#x2F;code&gt;, &lt;code&gt;awk&lt;&#x2F;code&gt;, &lt;code&gt;perl&lt;&#x2F;code&gt;, &lt;code&gt;make&lt;&#x2F;code&gt;), a lexer&#x2F;parser for preprocessing a custom &lt;code&gt;.verb&lt;&#x2F;code&gt; file format, &lt;code&gt;pdflatex&lt;&#x2F;code&gt; to compile the PDF, and &lt;code&gt;tex4ht&lt;&#x2F;code&gt; to generate HTML.
In 2026 we can use technology that was not available in 2010 and simplify the process of authoring the report significantly.
Of the available technologies, the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;typst.app&#x2F;&quot;&gt;Typst&lt;&#x2F;a&gt; typesetting system currently satisfies the constraints the best:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;It is open-source software available under the Apache 2.0 license. (An associated cloud service similar to Overleaf is proprietary, but entirely optional.)&lt;&#x2F;li&gt;
&lt;li&gt;Typst is implemented in Rust and has compile times that are roughly an order of magnitude faster than tex.
This allows to get instantaneous visual feedback when working on the report.&lt;&#x2F;li&gt;
&lt;li&gt;Both PDF and HTML output is supported natively, with no need to use separate tools.&lt;&#x2F;li&gt;
&lt;li&gt;The compiler emits fantastic error messages which point to the erroneous location in the code.&lt;&#x2F;li&gt;
&lt;li&gt;Simpler technologies (like plain Markdown) also allow to target multiple output formats, but they lack the advanced features to generate high-quality technical documentation, especially when it comes to the typesetting of mathematics and complex figures.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Using Typst means we have to convert the report from the custom &lt;code&gt;.verb&lt;&#x2F;code&gt; format to &lt;code&gt;.typ&lt;&#x2F;code&gt; files; fortunately this part of the work is completed, and the report can be compiled using a simple &lt;code&gt;typst compile&lt;&#x2F;code&gt; invocation. (The translation process did not involve LLMs, and was done manually using some &lt;code&gt;sed&lt;&#x2F;code&gt; and &lt;code&gt;awk&lt;&#x2F;code&gt; magic to facilitate the process.)
Re-typesetting the BNF grammars also allowed to make them hyperlinked: It is now possible to navigate the grammar by clicking on non-terminal symbols and to jump to their definition.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;documenting-the-standard-libraries&quot;&gt;Documenting the Standard Libraries&lt;&#x2F;h2&gt;
&lt;p&gt;The existing Haskell 2010 report consists of two parts: The first part documents the syntax and semantics of the language, and the second part documents the standard libraries.
Documenting the standard libraries is an essential part of the report, because many aspects of the language can only be defined in terms of functions, types and type classes available in the libraries. For example, numeric literals are polymorphic and have to be defined in terms of the numeric hierarchy, do-notation has to be defined in terms of the Monad (and MonadFail) type classes, and the foreign function interface (FFI) makes extensive use of functions and types defined in the &lt;code&gt;Foreign.*&lt;&#x2F;code&gt; namespace.&lt;&#x2F;p&gt;
&lt;p&gt;Maintaining hundreds of pages of standard library documentation in purely textual form (either as LaTeX or Typst files), and ensuring that they remain valid Haskell and consistent with each other is very hard.
If we want to avoid typos and inconsistencies sneeking into the report, and if we want to keep the library specification maintainable, then we have to organize it in some form which is amenable to machine checking.
The obvious solution is to document the API of the standard libraries using Haddock. The repository for the revised report contains a cabal package which exposes the precise API specified in the Haskell 2010 language report.
(This package only specifies the public API: The functions and methods themselves do not have an implementation. A possible stretch goal would be to use tools like &lt;code&gt;api-diff&lt;&#x2F;code&gt; or &lt;code&gt;print-api&lt;&#x2F;code&gt; (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discourse.haskell.org&#x2F;t&#x2F;maintain-a-golden-test-of-your-packages-api-with-diff-package-api-and-print-api&#x2F;9997&quot;&gt;Link&lt;&#x2F;a&gt;) to ensure that the API exposed by packages like &lt;code&gt;base&lt;&#x2F;code&gt; is a strict superset of the API defined in the report.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-process-shall-we-use&quot;&gt;What Process Shall we Use&lt;&#x2F;h2&gt;
&lt;p&gt;The Haskell Prime process worked using a combination of mailing list discussions and a Git repository which collected RFCs. Notably, the process did not work directly on the source code used for building the report.
We can reduce a lot of the overhead by switching to a collaboration process that is modelled more closely on how we develop code: Changes to the report should primarily be discussed in the form of pull requests to the textual sources of the report itself. The repository to organize the writing of the report is now public: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskellfoundation&#x2F;haskell-2010-revised-report&quot;&gt;github.com&#x2F;haskellfoundation&#x2F;haskell-2010-revised-report&lt;&#x2F;a&gt;. Please start contributing by discussing the existing issues, by contributing new issues, and by helping fix issues with the technical infrastructure. We will not yet merge any actual changes to the report, since these changes will need to be confirmed by the  new language committee that will be established, but we can start working on all other technical issues.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;call-to-action&quot;&gt;Call to Action&lt;&#x2F;h2&gt;
&lt;p&gt;In order for the revised Haskell 2010 report to enjoy broad support from the community, we have established a new &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.haskell.org&#x2F;hf&#x2F;meta&#x2F;-&#x2F;blob&#x2F;main&#x2F;working_groups&#x2F;revised-haskell-2010-report.md&quot;&gt;working group&lt;&#x2F;a&gt; under the umbrella of the Haskell Foundation. We now need your help and participation, ideally by deciding to become a member of the working group tasked with compiling the revised report. If you do not want to become a member of the working group you can still join all the discussions in the repository, contribute issues, and open pull requests with changes to the report, but becoming a member gives you the right to cast your vote on which changes to the report shall be adopted.&lt;&#x2F;p&gt;
&lt;p&gt;We will shortly be announcing a call to join the working group on the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discourse.haskell.org&#x2F;&quot;&gt;Haskell Discourse&lt;&#x2F;a&gt;, and soon thereafter start the work on the revised report.
If you always wished for Haskell to get a new report, or were discouraged by the lack of progress when it comes to standardization, now is your chance to contribute! With the clearly delimited scope set out above we can also be confident that the finish line is clearly in sight. And who knows, if the revised report is a clear success we might even have the appetite to follow up with something more ambitious...&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Quick tips for fast iteration in Haskell</title>
        <published>2026-07-28T00:00:00+00:00</published>
        <updated>2026-07-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/quick-tips-for-fast-iteration-in-haskell/"/>
        <id>https://blog.haskell.org/quick-tips-for-fast-iteration-in-haskell/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/quick-tips-for-fast-iteration-in-haskell/">&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;&#x2F;h2&gt;
&lt;p&gt;The Haskell community has recently been discussing how to achieve fast
iteration times when type checking and compiling Haskell code.  Fast
feedback has always been important in software development, and we&#x27;re
seeing increased interest in the topic in recent months due to the
rise in prominence of agentic coding.&lt;&#x2F;p&gt;
&lt;p&gt;This article describes two techniques for fast iteration: using &lt;code&gt;ghci&lt;&#x2F;code&gt;
to get fast feedback on the result of type checking an compilation,
and speeding up builds with a careful choice of flags to &lt;code&gt;ghc&lt;&#x2F;code&gt; and
&lt;code&gt;cabal&lt;&#x2F;code&gt;.  These techniques can be run on any existing codebase. They
don&#x27;t require you to restructure your code in any way, so you can get
an instant improvement to your iteration times. Let&#x27;s dive in!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ghci-based&quot;&gt;&lt;code&gt;ghci&lt;&#x2F;code&gt;-based&lt;&#x2F;h2&gt;
&lt;p&gt;GHCi is &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ghc.gitlab.haskell.org&#x2F;ghc&#x2F;doc&#x2F;users_guide&#x2F;ghci.html&quot;&gt;GHC&#x27;s interactive
interpreter&lt;&#x2F;a&gt;
(executable name &lt;code&gt;ghci&lt;&#x2F;code&gt;) which comes bundled with every installation
of GHC.  It is a REPL (&quot;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Read%E2%80%93eval%E2%80%93print_loop&quot;&gt;read-eval-print
loop&lt;&#x2F;a&gt;&quot;),
which means you can type expressions into it and it will run them,
printing the result.  &lt;code&gt;ghci&lt;&#x2F;code&gt; is a good starting point for fast
feedback because it doesn&#x27;t have to fully compile code, rather it
generates executable bytecode and skips machine code generation,
making it inherently faster than a normal build.&lt;&#x2F;p&gt;
&lt;p&gt;There are a variety of ways to use &lt;code&gt;ghci&lt;&#x2F;code&gt;, from using barebones &lt;code&gt;ghci&lt;&#x2F;code&gt;
itself directly to featureful wrappers.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ghci-itself&quot;&gt;&lt;code&gt;ghci&lt;&#x2F;code&gt; itself&lt;&#x2F;h3&gt;
&lt;p&gt;You can invoke &lt;code&gt;ghci&lt;&#x2F;code&gt; with a collection of source paths and it will
compile and load those modules, for example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ghci src&#x2F;Path&#x2F;Module1.hs src&#x2F;Path&#x2F;Module2.hs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or, perhaps more useful, use a &lt;code&gt;ghci&lt;&#x2F;code&gt; invocation wrapper that comes
with your build tool, &lt;code&gt;cabal&lt;&#x2F;code&gt; or &lt;code&gt;stack&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;stack ghci&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cabal repl&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For a simple &lt;code&gt;ghci&lt;&#x2F;code&gt; workflow, make some changes to the files in your
project, then navigate to your &lt;code&gt;ghci&lt;&#x2F;code&gt; window and type &lt;code&gt;:reload&lt;&#x2F;code&gt; (or
&lt;code&gt;:r&lt;&#x2F;code&gt; for short). &lt;code&gt;ghci&lt;&#x2F;code&gt; will type check and compiles your changes,
printing any errors or warnings from GHC.  Simple!&lt;&#x2F;p&gt;
&lt;p&gt;You might feel that continually typing &lt;code&gt;:r&lt;&#x2F;code&gt; ought to be automated
away. If so then check out the next section on &lt;code&gt;ghcid&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ghcid&quot;&gt;&lt;code&gt;ghcid&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ndmitchell&#x2F;ghcid&quot;&gt;&lt;code&gt;ghcid&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; is a wrapper around &lt;code&gt;ghci&lt;&#x2F;code&gt; that automates issuing &lt;code&gt;:r&lt;&#x2F;code&gt; when
any file in your project changes, so its workflow is even easier than
that of &lt;code&gt;ghci&lt;&#x2F;code&gt;: make some changes to the files in your project and
then merely &lt;em&gt;look at&lt;&#x2F;em&gt; your &lt;code&gt;ghcid&lt;&#x2F;code&gt; window; &lt;code&gt;ghcid&lt;&#x2F;code&gt; will have detected
your changes and the result of type check and compilation will appear
automatically.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;automatically-running-tests&quot;&gt;Automatically running tests&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;code&gt;ghcid&lt;&#x2F;code&gt; also allows you to get more quick feedback than just the
result of type checking and compilation: you can run tests (or indeed
any code) when your source files change.  There are two ways to do
this:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Embed comments with expressions to be evaluated. For example add
this comment to a source file to evaluate &lt;code&gt;expr&lt;&#x2F;code&gt; after loading:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- $&amp;gt; expr&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(Evaluating embedded comment expressions requires using the
&lt;code&gt;--allow-eval&lt;&#x2F;code&gt; flag.)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Pass an expression to the command line option &lt;code&gt;--test&lt;&#x2F;code&gt;, to run
after the code is loaded successfully, for example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ghcid --test myTestFun&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(By default the test expression will only run if the code is
warning-free.  To run the test expression even if there are
warnings, also pass &lt;code&gt;--warnings&lt;&#x2F;code&gt;.)&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;For more information on these features, see the
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ndmitchell&#x2F;ghcid#evaluation&quot;&gt;Evaluation&lt;&#x2F;a&gt; section
of the &lt;code&gt;ghcid&lt;&#x2F;code&gt; README.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;installation&quot;&gt;Installation&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;code&gt;ghcid&lt;&#x2F;code&gt; is a normal executable package on Hackage, so you can install
it with, for example, &lt;code&gt;cabal install ghcid&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ghcid-check&quot;&gt;&lt;code&gt;ghcid-check&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;One limitation of &lt;code&gt;ghcid&lt;&#x2F;code&gt;&#x27;s default behaviour is that it is not
&lt;em&gt;programatic&lt;&#x2F;em&gt;.  When you change source files, &lt;code&gt;ghcid&lt;&#x2F;code&gt;&#x27;s updates appear
in the terminal in which it is already running.  That&#x27;s no good if you
want to give fast feedback to a coding agent; ideally the coding agent
would run a command line executable to obtain the feedback.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s fine, because &lt;code&gt;ghcid&lt;&#x2F;code&gt; supports that too, with its &lt;code&gt;--reload&lt;&#x2F;code&gt; and
&lt;code&gt;--outputfile&lt;&#x2F;code&gt; options. But setting that up is a little fiddly, so
here&#x27;s a simple &lt;code&gt;ghcid&lt;&#x2F;code&gt; wrapper &lt;code&gt;bash&lt;&#x2F;code&gt; script called &lt;code&gt;ghcid-check&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tomjaguarpaw&#x2F;ghcid-check&#x2F;&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;tomjaguarpaw&#x2F;ghcid-check&#x2F;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;ghcid-check&lt;&#x2F;code&gt; is very basic and you might want to customize it to meet
your own particular needs.&lt;&#x2F;p&gt;
&lt;p&gt;Beyond &lt;code&gt;ghcid&lt;&#x2F;code&gt;, there are another couple of projects based on a
similar idea that bring new features: &lt;code&gt;ghciwatch&lt;&#x2F;code&gt; and Tricorder.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ghciwatch&quot;&gt;&lt;code&gt;ghciwatch&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mercurytechnologies&#x2F;ghciwatch#ghciwatch&quot;&gt;&lt;code&gt;ghciwatch&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;
is an updated take on &lt;code&gt;ghcid&lt;&#x2F;code&gt;, from the Haskell developers at
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mercury.com&#x2F;blog&#x2F;topics&#x2F;engineering-blog&quot;&gt;Mercury&lt;&#x2F;a&gt;.  Compared
to &lt;code&gt;ghcid&lt;&#x2F;code&gt; it has more sophisticated checking for new, removed and new
modules, which sometimes trip up &lt;code&gt;ghcid&lt;&#x2F;code&gt;, as well as other new
features.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tricorder&quot;&gt;Tricorder&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tweag&#x2F;tricorder#tricorder&quot;&gt;Tricorder&lt;&#x2F;a&gt; is from
Tweag and provides even more live status and diagnostic updates than
&lt;code&gt;ghcid&lt;&#x2F;code&gt; or &lt;code&gt;ghciwatch&lt;&#x2F;code&gt;, including access to documentation and
machine-readable output, and is designed from the ground up to be
usable programatically by coding agents as well as interactively by
humans.&lt;&#x2F;p&gt;
&lt;p&gt;Check out the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discourse.haskell.org&#x2F;t&#x2F;ann-tricorder-a-new-development-tool-for-haskell-and-llms&#x2F;14208&quot;&gt;announcement
post&lt;&#x2F;a&gt;
on the Haskell Discourse, which includes a short video of Tricorder
functionality. &lt;code&gt;tricorder&lt;&#x2F;code&gt; is a normal executable package on Hackage,
so you can install it with, for example, &lt;code&gt;cabal install tricorder&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cabal-based&quot;&gt;&lt;code&gt;cabal&lt;&#x2F;code&gt;-based&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s now see how to speed up development builds using the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.haskell.org&#x2F;cabal&#x2F;&quot;&gt;&lt;code&gt;cabal&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; build system. &lt;code&gt;cabal&lt;&#x2F;code&gt; has existed for a very long time, and you may be surprised by some of the things it offers!&lt;&#x2F;p&gt;
&lt;p&gt;Speeding up development builds using &lt;code&gt;cabal&lt;&#x2F;code&gt; involves three broad optimizations:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Tuning GHC&#x27;s runtime options;&lt;&#x2F;li&gt;
&lt;li&gt;Decreasing the amount of work &lt;code&gt;cabal&lt;&#x2F;code&gt; and GHC have to do by disabling compile-time optimizations;&lt;&#x2F;li&gt;
&lt;li&gt;Increasing resource-sharing for more effective build parallelism.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Before we describe the build optimizations above, it is worth understanding how cabal can be configured. Cabal uses &lt;em&gt;project&lt;&#x2F;em&gt; files to bundle configuration options related to multiple packages. The default configuration file, which is picked up automatically by &lt;code&gt;cabal&lt;&#x2F;code&gt;, is &lt;code&gt;cabal.project&lt;&#x2F;code&gt;. A typical &lt;code&gt;cabal.project&lt;&#x2F;code&gt; file for a project with three packages looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;packages:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    packageA&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    packageB&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    packageC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Recent versions of cabal allow project files to import other projects files. Therefore, without changing our default &lt;code&gt;cabal.project&lt;&#x2F;code&gt; file, we&#x27;ll create a new configuration file specifically for development builds. Let&#x27;s call it &lt;code&gt;cabal.fast.project&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;import: cabal.project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;As we go through build optimizations below, we&#x27;ll append to our &lt;code&gt;cabal.fast.project&lt;&#x2F;code&gt;. Then, a fast build will specify to use &lt;code&gt;cabal.fast.project&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt; cabal build all&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; --project-file=cabal.fast.project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That way, we have an easy set-up for fast builds. We could have project files for profiling builds, release builds, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Running the above command, we got a baseline compilation time on a private, commercial Haskell codebase of more than 150 000 lines of Haskell code. As we go through build optimizations below, we will report on the speedups achieved from this baseline.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tuning-ghc-s-runtime-options&quot;&gt;Tuning GHC&#x27;s runtime options&lt;&#x2F;h3&gt;
&lt;p&gt;GHC is a wonderful piece of technology. In some ways, it might as well be from the future. However, compiling Haskell programs into performant executables is hard work, and GHC is itself a Haskell program. This means we must tune its runtime system to squeeze out more performance.&lt;&#x2F;p&gt;
&lt;p&gt;Take a look at the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;ghc&#x2F;9.14.1&#x2F;docs&#x2F;users_guide&#x2F;runtime_control.html&quot;&gt;GHC 9.14.1 user guide&#x27;s section on runtime control&lt;&#x2F;a&gt;. In our experience, however, simply tuning the garbage collection allocation size is a great first step if your computer is somewhat recent. In particular, increasing the allocation size from the default of 4MB to 64MB, 128MB, er even 256MB may dramatically increase compilation throughput!&lt;&#x2F;p&gt;
&lt;p&gt;Thus, we update our &lt;code&gt;cabal.fast.project&lt;&#x2F;code&gt; project file to pass the appropriate flag to GHC. We&#x27;ll use an allocation area of 64MB, but do experiment on your own workloads:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;import: cabal.project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 1: tuning GHC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;program-options&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ghc-options: +RTS -A64m -RTS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Building with the above project file, we achieve a speedup of approximately 10%. Not huge, but not marginal either. Big gains remain!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;disabling-compile-time-optimizations&quot;&gt;Disabling compile-time optimizations&lt;&#x2F;h3&gt;
&lt;p&gt;Even if GHC is given the appropriate resources to do its job more effectively, it still does a lot of work by default, since GHC&#x27;s default optimization level is &lt;code&gt;-O1&lt;&#x2F;code&gt;. This means that GHC performs &lt;em&gt;some&lt;&#x2F;em&gt; optimizations, which necessarily requires more compilation time.&lt;&#x2F;p&gt;
&lt;p&gt;But this is merely the &lt;em&gt;default&lt;&#x2F;em&gt; behavior, and this article is all about speed. Instead, we can disable optimizations via the &lt;code&gt;optimization&lt;&#x2F;code&gt; option in cabal project files:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;import: cabal.project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 1: tuning GHC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;program-options&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ghc-options: +RTS -A64m -RTS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 2: disable optimizations&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;optimization: False&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that this optimization flag only applies to your locally-built packages. Your third-party dependencies will be compiled using the default optimization level, &lt;code&gt;-O1&lt;&#x2F;code&gt;. This is generally considered acceptable since your third-party dependencies are rarely built, and it allows you to re-use them for other build profiles.&lt;&#x2F;p&gt;
&lt;p&gt;At this stage, the codebase is building 2x faster!&lt;&#x2F;p&gt;
&lt;h3 id=&quot;better-resource-sharing-for-build-parallelism&quot;&gt;Better resource-sharing for build parallelism&lt;&#x2F;h3&gt;
&lt;p&gt;We&#x27;ve tuned GHC, and minimized the amount of work it needs to do. What is left is speed up the build across &lt;em&gt;multiple packages&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;cabal&lt;&#x2F;code&gt; has an option for parallel builds called &lt;code&gt;--jobs&lt;&#x2F;code&gt;. You can either specify a number of capabilities to dedicate to the build (e.g. &lt;code&gt;--jobs=2&lt;&#x2F;code&gt;), or let &lt;code&gt;cabal&lt;&#x2F;code&gt; decide based on your hardware (using a bare &lt;code&gt;--jobs&lt;&#x2F;code&gt;). Higher is not &lt;em&gt;always&lt;&#x2F;em&gt; better; this depends on your build graph!&lt;&#x2F;p&gt;
&lt;p&gt;In our experience, a number from 4 - 8 is appropriate to start with. We therefore update our &lt;code&gt;cabal.fast.project&lt;&#x2F;code&gt; project file:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;import cabal.project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 1: tuning GHC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;program-options&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ghc-options: +RTS -A64m -RTS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 2: disable optimizations&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;optimization: False&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 3: build parallelism&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jobs: 6&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We can do even better! If you use &lt;code&gt;cabal&lt;&#x2F;code&gt; 3.12+ in combination with GHC 9.8+, you can take advantage of a new flag, &lt;code&gt;--semaphore&lt;&#x2F;code&gt;, which allows &lt;code&gt;cabal&lt;&#x2F;code&gt; to share resources better with GHC. While &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;well-typed.com&#x2F;blog&#x2F;2023&#x2F;08&#x2F;reducing-haskell-parallel-build-times&#x2F;&quot;&gt;Well-Typed has a full breakdown of the why and how&lt;&#x2F;a&gt;, we summarize the result here. In short, if cabal can take advantage of multiple parallel invocations of GHC, it will do so; however, under the &lt;code&gt;--semaphore&lt;&#x2F;code&gt; option, if cabal &lt;em&gt;cannot&lt;&#x2F;em&gt; take advantage of all the cores it was allocated, it can tell GHC to use more cores. This can result in compilation time reductions of up to 30%!&lt;&#x2F;p&gt;
&lt;p&gt;We update our &lt;code&gt;cabal.fast.project&lt;&#x2F;code&gt; one final time:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;import: cabal.project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 1: tuning GHC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;program-options&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ghc-options: +RTS -A64m -RTS&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 2: disable optimizations&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;optimization: False&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-- Step 3: build parallelism&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jobs: 6&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;semaphore: True&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Finally, our builds will be much faster:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;$&lt;&#x2F;span&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt; cabal build all&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; --project-file=cabal.fast.project&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Compared to our baseline, we achieve a cumulative build speedup of 2.6x!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going further&lt;&#x2F;h2&gt;
&lt;p&gt;If you&#x27;re eager for even more build time improvements and are willing
to spend time adjusting the structure of your codebase, we can point
you where to look next: watch of Teo Camarasu&#x27;s talk at the 2026
Haskell Ecosystem Workshop, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=nmE6aa_I5TU&quot;&gt;Optimising for fast builds with
GHC&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;We hope you enjoyed our brief tour of using &lt;code&gt;ghci&lt;&#x2F;code&gt; and &lt;code&gt;cabal&lt;&#x2F;code&gt; for
fast iterative development in Haskell. If you have any comments or
questions, or have your own tips and techniques you can share with the
community, then please post on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discourse.haskell.org&#x2F;&quot;&gt;the Haskell
Discourse&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;&#x2F;h2&gt;
&lt;p&gt;Lots of people have already written up their experience of workflows
that &lt;code&gt;ghci&lt;&#x2F;code&gt;, including for fast iterative development. You might want
to check them out.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ghc.gitlab.haskell.org&#x2F;ghc&#x2F;doc&#x2F;users_guide&#x2F;ghci.html&quot;&gt;Using GHCi — GHC Users Guide&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;haskellweekly.news&#x2F;episode&#x2F;6.html&quot;&gt;Fast Feedback&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;academy.fpblock.com&#x2F;blog&#x2F;2018&#x2F;08&#x2F;haskell-development-workflows-4-ways&#x2F;&quot;&gt;Haskell Development Workflows (4 ways)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.parsonsmatt.org&#x2F;2018&#x2F;05&#x2F;19&#x2F;ghcid_for_the_win.html&quot;&gt;ghcid for the win!&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;functor.tokyo&#x2F;blog&#x2F;2019-04-07-ghcid-for-web-app-dev&quot;&gt;ghcid for Web App Development&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mercury.com&#x2F;blog&#x2F;announcing-ghciwatch&quot;&gt;Announcing ghciwatch 1.0&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jeancharles.quillet.org&#x2F;posts&#x2F;2024-09-04-Haskell-dev-workflow-with-ghcid-and-neovim.html&quot;&gt;Haskell dev workflow with ghcid and neovim&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.well-typed.com&#x2F;blog&#x2F;2023&#x2F;03&#x2F;cabal-multi-unit&#x2F;&quot;&gt;Multiple Component support for cabal repl&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alexfmpe&#x2F;semantic-satiation&#x2F;blob&#x2F;main&#x2F;posts&#x2F;002-cheaper.md&quot;&gt;Cheaper: producing a program with less developer time&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;rapid&#x2F;docs&#x2F;Rapid.html&quot;&gt;Rapid&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Enterprise Haskell at H-E-B</title>
        <published>2026-07-16T00:00:00+00:00</published>
        <updated>2026-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/enterprise-haskell-at-h-e-b/"/>
        <id>https://blog.haskell.org/enterprise-haskell-at-h-e-b/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/enterprise-haskell-at-h-e-b/">&lt;p&gt;&lt;em&gt;This article is the second installment of our series &quot;&lt;a href=&quot;&#x2F;categories&#x2F;haskellers-from-the-trenches&#x2F;&quot;&gt;Haskellers from the trenches&lt;&#x2F;a&gt;&quot;,
where we invite experienced engineers to talk about their subjects of expertise, best practices, and production tales with Haskell&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-mainframe-problem&quot;&gt;The mainframe problem&lt;&#x2F;h2&gt;
&lt;p&gt;When I joined &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.heb.com&quot;&gt;H-E-B&lt;&#x2F;a&gt; back in 2018, I was entering a world far removed from any
of my previous roles in technology. H-E-B is a &lt;em&gt;retail&lt;&#x2F;em&gt; company first, and the
largest privately held company in Texas. Our customers care about full shelves,
not the systems behind them, and for decades those systems delivered:
rock-solid COBOL mainframes doing their job reliably, day in and day out.
Reliability like that is no small thing. But customer expectations have
shifted. Curbside pickup, home delivery, real-time inventory, in-store search,
and many other tech-heavy features have really only emerged in the last 10
years, and meeting that demand means scaling supply chain operations across
435+ stores and the distribution centers that feed them. That kind of growth
asks more of our core systems than they were originally designed to give,
especially as the grocery business turns more digital and more competitive by
the year.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s no secret that a lot of the world still runs on mainframes. There&#x27;s
usually little incentive to spend the capital on a massive lift-and-shift into
the cloud when current systems maintain the status quo well enough. Copying
code from one machine to another is hardly the real challenge here. Usually
these kinds of projects fail to deliver because they lack the bespoke
institutional knowledge encoded in decades of legacy business logic. How do you
take a monolithic application operating on hundreds of tables and split it out
into microservices, owned and operated by their own autonomous engineering
teams? It&#x27;s both a technological and political undertaking of enormous
proportions, and an expensive one. Whatever product comes out on the other end,
you can bet that the business owners don&#x27;t want to repeat this process ever
again.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;enter-the-lambda&quot;&gt;Enter the lambda&lt;&#x2F;h2&gt;
&lt;p&gt;Haskell was originally pitched by consultants as the lingua franca for a new
initiative to replace the largest mainframe application in the company. I came
onto this new project in its infancy, ready and giddy to use Haskell on a
real-world project with real-world impact. What started as an experiment grew
into multiple production systems now running critical supply chain
applications. Eight years on, those systems are still in production, with
nearly a million lines of Haskell maintained by several teams across different
domains. So there have been real successes, but also real struggles. I hope that
my experience on these projects can translate into some valuable lessons for
those who wonder about or may already be running Haskell programs in
production.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-haskell&quot;&gt;Why Haskell?&lt;&#x2F;h2&gt;
&lt;p&gt;Pitching a programming language as a business solution is an uphill battle.
Haskell is not known for being an enterprise language, and while it thrives in
programming-language research, it lost the foothold it once had in the intro
course, despite Dijkstra&#x27;s famous defense of it over Java in his &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.cs.utexas.edu&#x2F;~EWD&#x2F;transcriptions&#x2F;OtherDocs&#x2F;Haskell.html&quot;&gt;letter to the
UT Austin Budget
Council&lt;&#x2F;a&gt;.
But &quot;enterprise language&quot; says more about market forces than language design.
The same momentum that filled the intro course with Java fills the hiring pool
and the vendor SDK lists, and nearly all of that momentum belongs to imperative
languages, deemed easier to hire for, and these days, easier to prompt for. The
enterprise cares not for laziness, pure functions, immutable data, or anything
that weighs lambda calculus against object-oriented orthodoxy. The enterprise
just wants another COBOL: a system that runs for 40 more years without
null-pointer exceptions and with a lower maintenance burden. I firmly believe
that Haskell fits into this niche. But it&#x27;s not trivial to translate the
benefits of a souped-up polymorphic lambda calculus and software-transactional
memory into a business case.&lt;&#x2F;p&gt;
&lt;p&gt;So what&#x27;s my pitch?&lt;&#x2F;p&gt;
&lt;p&gt;I can tell our CTO that a Haskell application will suffer fewer classes of
runtime failure by default, ruling out the whole category of bugs that
imperative and dynamically typed programs encounter on a daily basis. There
will still be bugs, but the ones that remain tend to be the interesting ones in
our business logic, rather than null-pointer exceptions and type confusion.&lt;&#x2F;p&gt;
&lt;p&gt;In the face of multi-year strangler fig migrations of legacy systems, I can
tell Haskell developers that &lt;em&gt;it&#x27;s ok to make mistakes&lt;&#x2F;em&gt;. They can iterate
while the business requirements are still taking shape, because GHC gives
them the tools for painless refactoring. That freedom is what lets a
migration run like an agile project instead of a waterfall. The business
measures results incrementally, rather than paying up front for an incorrect,
crystallized product that will be abandoned in a year.&lt;&#x2F;p&gt;
&lt;p&gt;Integrating with legacy systems is bread-and-butter work for Haskell programs.
By chance do you need to parse thousands of lines of fixed-width IBM copybooks?
Parsers in Haskell are best in class. A legacy system that can&#x27;t easily emit
XML or JSON doesn&#x27;t have to, because its native format becomes the contract.
Need to hook into old services that only offer C libraries? Haskell has one of
the best foreign-function interfaces in the game. We&#x27;ve written Haskell
libraries around simdjson (C++), Oracle&#x27;s ODPI-C, and IBM&#x27;s MQ, to name a few.&lt;&#x2F;p&gt;
&lt;p&gt;The dearth of Haskell SDKs in the wild looks like a showstopper at first. In
practice, you take on the maintenance burden forever, and in exchange you own
your contracts outright, avoid vendor lock-in, and never wait on an upstream
maintainer to ship a fix. For critical dependencies, we&#x27;ve found that trade
well worth making. Today the velocity gap is narrower than it looks, since
porting your favorite Go library to Haskell is the kind of mechanical work a
coding agent does well. Unfortunately, rote translations like that may not
take full advantage of the type system without clever prompting.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a subtler advantage that makes Haskell a compelling core enterprise
language: its strength as a host for embedded DSLs. A central team can encode a
domain model or business logic once in a Haskell eDSL and generate safe, typed
code in whatever language other teams already use, letting them hook into your
system without ever writing Haskell themselves.
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;CategoricalData&#x2F;hydra&quot;&gt;Hydra&lt;&#x2F;a&gt;, an open-source project that
grew out of graph-data work at Uber, is a striking example. It&#x27;s a
Haskell-bootstrapped DSL framework that generates semantically-equivalent
implementations across Java, Python, Scala, and more, with a shared test suite
guaranteeing parity. You keep Haskell&#x27;s correctness guarantees at the source
while meeting every other team where they already are.&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ve explored this embedded DSL idea in various ways at H-E-B. In one case,
we prototyped a Haskell DSL for sensitive financial calculations that could be
shared across our Haskell, TypeScript, and Java teams. In another, we built a
Haskell DSL for bidirectional JSON schemas, similar in spirit to the
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;autodocodec&quot;&gt;autodocodec&lt;&#x2F;a&gt; library, but
using a tagless final encoding that allows users to extend the core semantics
with their own type classes. It lets our Haskell teams standardize on schema
versioning strategies, documentation&#x2F;OpenAPI generation, and regression&#x2F;golden
testing. For anything else, developers can write ad-hoc interpreters, e.g.
converting a JSON schema to a PostgreSQL table definition, swapping in
&lt;code&gt;simdjson&lt;&#x2F;code&gt; for &lt;code&gt;aeson&lt;&#x2F;code&gt;, or layering on additional validation.&lt;&#x2F;p&gt;
&lt;p&gt;I could go on about what Haskell offers programmers in the way of type safety,
purity, and elegant concurrency, but these days most modern languages offer
many of the same features (some of them inspired by Haskell itself). The
difference lies in the kind of engineering culture the language promotes. Ian
Duncan&#x27;s &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.haskell.org&#x2F;a-couple-million-lines-of-haskell&#x2F;&quot;&gt;post&lt;&#x2F;a&gt;
covers this territory well, and I risk parroting it here. Suffice to say, his
general advice holds. Haskell attracts idealists who care about correctness and
think carefully about the abstractions they build with. Hiring for a Haskell
role has never been the problem that leaders think it will be. If anything,
candidates came to us for the Haskell itself, even when they had no particular
interest in supply chain.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-not-haskell&quot;&gt;Why not Haskell?&lt;&#x2F;h2&gt;
&lt;p&gt;Let&#x27;s get real for a little bit. Haskell has been around for a long time, it
has warts, and it really pays to stay current with the latest GHC releases.
Without an expert on your team, all it takes is one wart to fester and infect
the entire project. In the beginning, we were fortunate to have consultants
with extensive Haskell experience. We were prescribed guardrails and some basic
rules to ensure we didn&#x27;t get overwhelmed by language extensions and custom
operator line noise.&lt;&#x2F;p&gt;
&lt;p&gt;Back in those days we didn&#x27;t yet have the GHC2021 or GHC2024 language editions,
so a sane baseline of extensions was something each team had to curate by hand.
That baseline was really a complexity budget - a cap on how much language a new
hire has to absorb before they can contribute. And enforcing it turns out to be
a losing game, because your dependencies spend the budget for you. Once you
pull in something like &lt;code&gt;servant&lt;&#x2F;code&gt; or &lt;code&gt;haxl&lt;&#x2F;code&gt;, you&#x27;ve just blown through the whole
budget on what the business sees as plumbing. Serving web requests with
&lt;code&gt;servant&lt;&#x2F;code&gt; means encoding your API as a type-level DSL, and extending it with
your own combinators drags in type families and the inscrutable errors that
come with them. Batching concurrent operations with &lt;code&gt;haxl&lt;&#x2F;code&gt; means modeling every
data source as a GADT indexed by its result type. These are elegant designs,
but they are not free.&lt;&#x2F;p&gt;
&lt;p&gt;As beginners become adepts and adepts become veterans, the perception of a
complexity budget begins to fade. More advanced features become tempting to
use, and their adoption weakens our promise to the business of easy
maintainability. New hires need to understand GADTs, type families, generics,
tagless final encodings, arrows, free structures, higher-kinded data type
patterns, and other shenanigans on top of an already dense business domain. It
wasn&#x27;t that we started out wanting to use all of these advanced patterns and
features of the language, nor could we really. Their viability emerged over
time as engineers became more familiar with the ecosystem. And that gradual
creep can really sneak up on you.&lt;&#x2F;p&gt;
&lt;p&gt;&quot;Boring&quot; Haskell is easier to preach than to enforce at an organizational
level. We engineers get, well, bored, and we are often tempted to leverage GHC
features for problems that may not warrant them. The key point here is an
intentional echo from Ian&#x27;s post, which is that encapsulating advanced Haskell
behind sane interfaces reduces the blast radius and scopes your complexity
budget to smaller libraries maintained by a handful of experts. The risk of
exploding complexity at the language level is hardly unique to Haskell, but few
languages offer so much rope. The business may measure complexity in lines of
code or tokens spent, but a better gauge for a Haskell project is how much of
it will break with the next GHC upgrade.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;trials-by-fire&quot;&gt;Trials by fire&lt;&#x2F;h2&gt;
&lt;p&gt;A sobering truth is that not all teams are created equal. Haskell can empower
one team while disempowering another. If this were not true, we would have more
Haskell teams than we do today. I&#x27;ve had to watch Haskell fail to stick on two
separate projects, and for seemingly unrelated reasons. In one case, a nasty
space leak brought the entire program to a halt. This was a team of seasoned
developers with no Haskell experience at all, using a project template copied
from a sibling team. They were simply hoping to follow the conventions they
inherited, but without a strong Haskell developer to guide them along, the
space leak was enough to kill the entire endeavor. In the other case, the
engineers who advocated for Haskell and built the applications left to work on
other projects - priorities shifted, nothing to do with the language.&lt;&#x2F;p&gt;
&lt;p&gt;In both cases a fallback option was always available. Engineers
gravitated back to what they wrote before Haskell, unconvinced that it was
making them more productive. This is the consequence of viewing Haskell as just
another means to an end, as just another programming language. The engineers
learned the language well enough. They just never found a passion for it.
Ultimately, as saccharine as it may sound, this is the differentiator for a
successful Haskell project in the enterprise. Nobody will be motivated to
diagnose a space leak if there&#x27;s an escape hatch to a Spring Boot template,
and now that LLMs can mechanically translate whole codebases, that escape hatch
is wider than ever. No codebase is too clever to rewrite, so the only real
defense is to build a team that would rather keep the Haskell.&lt;&#x2F;p&gt;
&lt;p&gt;Even on teams that embrace Haskell, there were some harrowing moments
that made us reflect on our choices. We ran into double-free errors stemming
from &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;hdbc&#x2F;hdbc-postgresql&#x2F;pull&#x2F;52&quot;&gt;custom reference counting
logic&lt;&#x2F;a&gt;, and we disabled
parallel garbage collection on one project because it was the only thing that
fixed a runaway space leak. In the worst case, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iand675&#x2F;datadog&#x2F;pull&#x2F;33&quot;&gt;infinite
loops&lt;&#x2F;a&gt; are one of the scariest bugs
simply because &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discourse.haskell.org&#x2F;t&#x2F;debugging-program-with-infinite-loop-somewhere&#x2F;13111&quot;&gt;finding them is
hard&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The GHC runtime itself has never been the root cause of a production outage
for us. In steady state, its throughput and memory footprint have been more
than competitive for our workloads, and the war stories above were exceptions
rather than the norm. What matters here is that engineers were able to fix these
problems by tracing through segfault core dumps, garbage collection metrics,
and heap profiles. A crucial lesson learned is that test coverage cannot be
sacrificed in the name of a strong type system. In fact, I wager that Haskell
applications are prime candidates for chaos engineering. Because whole
categories of trivial errors are compiled away, injecting infrastructure faults
surfaces the genuine logic and resource failures of your system instead of the
noise that would dominate a dynamically typed program.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;times-have-changed&quot;&gt;Times have changed&lt;&#x2F;h2&gt;
&lt;p&gt;When we started building our systems, we were on GHC 8.2. We had to write
most of our own API clients and SDKs for popular platforms. The Haskell language
server did not exist. GHCup was brand new. Profiling the runtime was a dark
art. If you&#x27;d asked me back then whether Haskell would increase velocity, I&#x27;d
have had some caveats. The business wants everything off the shelf, it doesn&#x27;t
want to pay to reinvent wheels, and we had to spend resources on building what
we needed. Developers coming from their Java IDEs recoiled at the state of
Haskell tooling. H-E-B even went so far as to sponsor development of Debug
Adapter Protocol support for Haskell (for VS Code users).&lt;&#x2F;p&gt;
&lt;p&gt;But now, after years of hard work on GHC and across the ecosystem, building
production-grade Haskell applications has gone from merely viable to genuinely
competitive.&lt;&#x2F;p&gt;
&lt;p&gt;For tooling we have HLS, &lt;code&gt;ghcid&lt;&#x2F;code&gt;, &lt;code&gt;ghciwatch&lt;&#x2F;code&gt;, &lt;code&gt;static-ls&lt;&#x2F;code&gt;,
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;atelier-hub&#x2F;tricorder&quot;&gt;tricorder&lt;&#x2F;a&gt;, and first-class code
editor plugins for Haskell. Some of us at H-E-B use GHC plugins to facilitate
richer GHCi-driven development, while some of us use VS Code and HLS, and we
cherish the freedom of developers to use their preferred tools.&lt;&#x2F;p&gt;
&lt;p&gt;For observability we now have &lt;code&gt;hs-opentelemetry&lt;&#x2F;code&gt;, and we use
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;aaronallen8455&#x2F;opentelemetry-auto&quot;&gt;opentelemetry-auto&lt;&#x2F;a&gt;, a
GHC plugin that autoinstruments Haskell functions with OTel spans. If the
business says they&#x27;d prefer Java or Python for their instrumentation story, we
can point at this and say Haskell has the same story.&lt;&#x2F;p&gt;
&lt;p&gt;The library shelf has filled out as well. Our services run on
workhorses like &lt;code&gt;warp&lt;&#x2F;code&gt;, &lt;code&gt;aeson&lt;&#x2F;code&gt;, &lt;code&gt;attoparsec&lt;&#x2F;code&gt;, &lt;code&gt;stm&lt;&#x2F;code&gt;, &lt;code&gt;conduit&lt;&#x2F;code&gt;, &lt;code&gt;servant&lt;&#x2F;code&gt;, and
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;orville-postgresql&quot;&gt;orville-postgresql&lt;&#x2F;a&gt;.
Where we find gaps, we write our own. For example,
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;velveteer&#x2F;arbiter&quot;&gt;arbiter&lt;&#x2F;a&gt;, a transactional PostgreSQL job
queue I maintain, was born from our work at H-E-B and runs in production
today.&lt;&#x2F;p&gt;
&lt;p&gt;All of this means developers and operations can both stop shuddering when
someone says &quot;this application uses Haskell&quot;. It&#x27;s only getting better as GHC
improves its own runtime diagnostics, compiler performance, monitoring features
(e.g. eventlog streaming), and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;well-typed.com&#x2F;blog&#x2F;2026&#x2F;05&#x2F;lay-annotation-land&#x2F;&quot;&gt;exception
annotations&lt;&#x2F;a&gt;. For the
record, I haven&#x27;t feared a space leak since info table profiling landed in GHC
9.2.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;haskell-culture&quot;&gt;Haskell culture&lt;&#x2F;h2&gt;
&lt;p&gt;Internal libraries matter a great deal in our organization, and we encourage
developers to work on both internal and open-source projects. We&#x27;ve been
running a Haskell meetup every other week for years, hoping to attract interest
across the company, sometimes succeeding. The meetups serve as an open forum for
engineers to share their own Haskell explorations and teach others.
Cross-pollination of ideas and patterns matters, because silos are everywhere
in the enterprise. This is also our answer to the bus-factor risk.
The more engineers who can read and own a codebase, the less any single
departure can sink it.&lt;&#x2F;p&gt;
&lt;p&gt;How do we empower developers while simultaneously advocating for boring
Haskell? Certainly we set some loose ground rules, but tech leads make the
final decisions on how to spend their complexity budgets. If a team must store
PII encrypted at rest, it will likely pay off to add type-level guarantees that
make leaking sensitive data a compile error rather than a runtime incident,
the kind of assurance security reviews actually care about. If a team is tiring
of boilerplate and wants to consolidate four API contracts using a
trees-that-grow pattern, it may not be worth obscuring the public-facing
schemas with type families.&lt;&#x2F;p&gt;
&lt;p&gt;For us, boring Haskell doesn&#x27;t preclude type-level programming or advanced
GHC features. It&#x27;s more that we establish principled conventions and weigh the
costs of straying from them. A big one is &quot;no spooky action at a distance&quot;,
e.g. no &lt;code&gt;TemplateHaskell&lt;&#x2F;code&gt; code generation, no &lt;code&gt;Generic&lt;&#x2F;code&gt;-derived serialization
logic, etc. The convention in this case is to write boilerplate and keep
everything explicit for the programmer. It&#x27;s not as draconian as it sounds,
since teams who want quasiquoters and deriving via &lt;code&gt;Generically&lt;&#x2F;code&gt; are not
forbidden outright. We want healthy skepticism when an engineer implements the
type-level state machine for a web form, or reaches for a GADT when a regular
ADT would do. An engineer can argue that their complexity is essential, but it
takes the whole team, and a culture where pushback is welcome, to spot when
it&#x27;s accidental.&lt;&#x2F;p&gt;
&lt;p&gt;That said, there&#x27;s plenty of advanced Haskell across our projects in both
libraries and production applications. I think it goes to show that you don&#x27;t
have to stay bored forever. You just have to limit the scope of your playtime.
We use Haskell because we want to work &lt;em&gt;and&lt;&#x2F;em&gt; play, and somewhere in the middle
is where engineering happens.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-road-ahead&quot;&gt;The road ahead&lt;&#x2F;h2&gt;
&lt;p&gt;I am incredibly optimistic about Haskell and GHC&#x27;s future in industry. Through
many years of using GHC we&#x27;ve found and reported a handful of bugs, none of
which were game-breaking for us. Production incidents are rare, and the
few we do see trace back to business logic far more often than to type errors
or the runtime.&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s also a new argument forming in Haskell&#x27;s favor. Coding agents now write
a growing share of our code, which shifts the bottleneck from writing code to
verifying it, and a compiler like GHC is well suited to that job. The same
type system that makes refactoring painless for humans gives an agent a tight
feedback loop that rejects hallucinated code before it runs.&lt;&#x2F;p&gt;
&lt;p&gt;We need more stories of Haskell&#x27;s success, and I am grateful that the Haskell
Foundation is making the effort to bring them to light.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Overview of Haskell security tooling</title>
        <published>2026-06-29T00:00:00+00:00</published>
        <updated>2026-06-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/overview-of-haskell-security-tooling/"/>
        <id>https://blog.haskell.org/overview-of-haskell-security-tooling/</id>
        
        <summary type="html">&lt;p&gt;Here is an overview of the Haskell security infrastructure, detailing the security team, the advisory database, the libraries developed for security metadata, and how downstream tools leverage this data.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Making GHC upgrades easy</title>
        <published>2026-06-12T00:00:00+00:00</published>
        <updated>2026-06-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/making-ghc-upgrades-easy/"/>
        <id>https://blog.haskell.org/making-ghc-upgrades-easy/</id>
        
        <summary type="html">&lt;h2 id=&quot;what-this-post-is-about&quot;&gt;What this post is about&lt;&#x2F;h2&gt;
&lt;p&gt;When a shiny new version of GHC comes out, it should be easy to upgrade.  After all, the new compiler should be more capable than the previous version!&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Contributing to Haskell Through a Beginner’s Lens</title>
        <published>2026-05-31T00:00:00+00:00</published>
        <updated>2026-05-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/contributing-to-haskell-through-a-beginner-s-lens/"/>
        <id>https://blog.haskell.org/contributing-to-haskell-through-a-beginner-s-lens/</id>
        
        <summary type="html">&lt;p&gt;October 2025 was when I wrote my first line of Haskell. Fast forward to May 2026, I am consistently contributing to the Haskell Language Server (HLS) and my Google Summer of Code proposal also got accepted. This blog is a mix of my personal experiences along with general learnings that could help beginners to start contributing to Haskell repositories.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Haskell Language Server 2.14.0.0 release</title>
        <published>2026-04-30T00:00:00+00:00</published>
        <updated>2026-04-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/hls-2-14-0-0/"/>
        <id>https://blog.haskell.org/hls-2-14-0-0/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/hls-2-14-0-0/">&lt;p&gt;The HLS team is excited to announce the &lt;code&gt;2.14.0.0&lt;&#x2F;code&gt; Haskell Language Server release!&lt;&#x2F;p&gt;
&lt;p&gt;You can install it today with GHCup: &lt;code&gt;$ ghcup install hls --set 2.14.0.0&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;features&quot;&gt;Features&lt;&#x2F;h2&gt;
&lt;p&gt;This new HLS version adds support for the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;ghc&#x2F;latest&#x2F;docs&#x2F;users_guide&#x2F;exts&#x2F;template_haskell.html#extension-ExplicitLevelImports&quot;&gt;&lt;code&gt;ExplicitLevelImports&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; language extension, which was added in GHC 9.14.1 in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4846&quot;&gt;#4846&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;HLS now starts up faster in &lt;code&gt;sessionLoading: multipleComponents&lt;&#x2F;code&gt; mode and handles out-of-project files more gracefully thanks to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4445&quot;&gt;#4445&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4791&quot;&gt;#4791&lt;&#x2F;a&gt; adds lots of great quality of life improvements when using Notes such as hover information and autocompletions for note references and declarations.&lt;&#x2F;p&gt;
&lt;p&gt;Additionally, this version introduces smart-case matching when completing modules and file paths in cabal files.&lt;&#x2F;p&gt;
&lt;p&gt;As of &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4621&quot;&gt;#4621&lt;&#x2F;a&gt; is now able to recognise file types based on the provided language kind instead of by file ending only.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;some-notable-bug-fixes&quot;&gt;Some notable bug fixes&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;HLS will now only show linear arrows when the LinearTypes language extension is enabled &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4862&quot;&gt;#4862&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;HLS will no longer offer &#x27;Add Argument&#x27; for identifiers which include qualified names &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4852&quot;&gt;#4852&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;When expanding nested records, HLS will now expand the record the cursor is pointing to instead of the outermost one &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4813&quot;&gt;#4813&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;HLS now names the code action for &lt;code&gt;record syntax conversion&lt;&#x2F;code&gt; as such instead of it being called  &lt;code&gt;wildcard expansion&lt;&#x2F;code&gt; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;haskell-language-server&#x2F;pull&#x2F;4819&quot;&gt;#4819&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;supported-ghc-versions&quot;&gt;Supported GHC versions&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;9.14.1&lt;&#x2F;li&gt;
&lt;li&gt;9.12.4&lt;&#x2F;li&gt;
&lt;li&gt;9.12.2&lt;&#x2F;li&gt;
&lt;li&gt;9.10.3&lt;&#x2F;li&gt;
&lt;li&gt;9.8.4&lt;&#x2F;li&gt;
&lt;li&gt;9.6.7&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;things-to-look-forward-to-gsoc-2026&quot;&gt;Things to look forward to - GSOC 2026&lt;&#x2F;h2&gt;
&lt;p&gt;We are very happy to announce that HLS has two exciting upcoming projects our Google Summer of Code students will be working on this summer!&lt;&#x2F;p&gt;
&lt;p&gt;@vidit-od will be working on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;summer.haskell.org&#x2F;ideas.html#goto-third-party-deps&quot;&gt;goto third-party definition&lt;&#x2F;a&gt;, which will enable developers to view source code of external libraries by clicking goto definition on them. Within external libraries, many of the usual intellisense will keep working such as hover or goto references.&lt;&#x2F;p&gt;
&lt;p&gt;@Aster89 will be working on the much sought after &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;summer.haskell.org&#x2F;ideas.html#casesplit&quot;&gt;case split plugin&lt;&#x2F;a&gt;. This plugin will add refactoring options for &lt;code&gt;case X of&lt;&#x2F;code&gt; which supplies holes for all possible constructors of &lt;code&gt;X&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;thank-you-haskell-community&quot;&gt;Thank you, Haskell Community&lt;&#x2F;h2&gt;
&lt;p&gt;We wish all Haskellers happy hacking while using the latest releases of the Haskell Language Server, and hope to see you in the issue tracker or even in some pull requests!&lt;&#x2F;p&gt;
&lt;p&gt;Finally, a reminder that you can donate to the development of HLS via &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;opencollective.com&#x2F;haskell-language-server&quot;&gt;OpenCollective&lt;&#x2F;a&gt;. The OpenCollective money pays for tedious, but important maintenance work and, sometimes, for getting new features over the finish line.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>What Would You See Changed in Haskell?</title>
        <published>2026-04-02T00:00:00+00:00</published>
        <updated>2026-04-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/what-would-you-see-changed-in-haskell/"/>
        <id>https://blog.haskell.org/what-would-you-see-changed-in-haskell/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/what-would-you-see-changed-in-haskell/">&lt;p&gt;This report condenses the free form answers to Question 77 of the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discourse.haskell.org&#x2F;t&#x2F;state-of-haskell-2025-results&#x2F;13755&quot;&gt;State of Haskell Survey 2025&lt;&#x2F;a&gt;.
Due to the phrasing of the question, we have received 553 answers, with varying degrees of precision, actionability, and relevance.
A team of two people (Sawa &amp;amp; Hécate) proceeded to quantitatively and qualitatively analyse the survey results,
extracting trends and topics about what the community would like to see changed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;interpreting-the-answers&quot;&gt;Interpreting The Answers&lt;&#x2F;h2&gt;
&lt;p&gt;A qualitative analysis of survey answers entails the hard task of noticing trends and moods, as well as determining the relevance of answers.
We needed to understand if an answer is an example of the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;XY_problem&quot;&gt;XY Problem&lt;&#x2F;a&gt;,
and if so establish what the deeper problem is. Some answers were simply too vague to be classified either as a trending mood, or an actionable suggestion.
Finally, one important thing to do when performing such an analysis is to have empathy for the people who spent their time responding to the survey and not outright dismiss their answers.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;major-topics&quot;&gt;Major Topics&lt;&#x2F;h2&gt;
&lt;p&gt;The following topics are the ones that have collected the most answers from the survey participants. They are presented in no particular order.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;switching-apis-to-using-text-instead-of-string&quot;&gt;Switching APIs to using &lt;code&gt;Text&lt;&#x2F;code&gt; instead of &lt;code&gt;String&lt;&#x2F;code&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;A laser-focused suggestion that came up is the replacement of &lt;code&gt;String&lt;&#x2F;code&gt; by &lt;code&gt;Text&lt;&#x2F;code&gt; as the canonical type for textual data. In particular, one answer explains the reasoning:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;text&lt;&#x2F;code&gt; library must live from &lt;code&gt;base&lt;&#x2F;code&gt;,  because none of the APIs of &lt;code&gt;base&lt;&#x2F;code&gt; can use &lt;code&gt;Text&lt;&#x2F;code&gt;, and when people look
at base for examples of well-written Haskell code, they end up using &lt;code&gt;String&lt;&#x2F;code&gt;, which is not the right choice.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This has been a common source of frustration for beginners and advanced users alike.
The topic was especially discussed in 2022 on the Discourse: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discourse.haskell.org&#x2F;t&#x2F;bringing-data-text-into-base-what-is-the-next-step&#x2F;5016&#x2F;&quot;&gt;&lt;em&gt;Bringing Data.Text into &lt;code&gt;base&lt;&#x2F;code&gt;: What is the next step?&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;onboarding-of-beginners&quot;&gt;Onboarding of Beginners&lt;&#x2F;h3&gt;
&lt;p&gt;Concerns regarding the quality of resources for beginners have been expressed in large numbers. Up-to-date content, and a centralised entry point in the language are some of the most actionable suggestions that we received.
The Haskell Wiki is especially well-referenced by search engines, but has seen a steady decrease in contributions. People are encouraged to apply for an account and contribute to the articles.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;documentation&quot;&gt;Documentation&lt;&#x2F;h3&gt;
&lt;p&gt;This one will not be a surprise to anyone, but thankfully some of the insights that were received pointed to concrete improvements that could be made. Library authors are encouraged to provide more &lt;em&gt;working&lt;&#x2F;em&gt; examples, as well as documentation that goes beyond the scope of a simple API reference.&lt;&#x2F;p&gt;
&lt;p&gt;Community gatherings lend themselves especially well to documentation contributions, as people needing the documentation can meet with people who are best positioned to write it.&lt;&#x2F;p&gt;
&lt;p&gt;For more information about what types of documentation exist and when to use particular ones, you can read &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.haskell.org&#x2F;documentation-best-practices-in-2024&#x2F;&quot;&gt;Documentation Best Practices in 2024&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;complexity-of-the-haskell-language&quot;&gt;Complexity of the Haskell Language&lt;&#x2F;h3&gt;
&lt;p&gt;We have noticed a trend amongst participants that the Haskell Language is perceived as too complex, riddled with historical cruft, and that there is a lack of consistency in how the language and its libraries have evolved over the decade.&lt;&#x2F;p&gt;
&lt;p&gt;Avoiding partial functions in the Prelude and more generally sane &#x2F; safe defaults are a recurring suggestion. People want to feel proud of the &lt;code&gt;base&lt;&#x2F;code&gt; library, where the strengths of Haskell can shine, and not be just a hodgepodge of convenient functions.&lt;&#x2F;p&gt;
&lt;p&gt;There have been requests to stop adding language extensions to GHC or even re-design the existing ones with a holistic approach. This feedback is in line with answers asking for a new Haskell Language Report, which could take advantage of the extensions that GHC has introduced over the years&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-record-system&quot;&gt;The Record System&lt;&#x2F;h3&gt;
&lt;p&gt;A certain proportion of survey participants have expressed their dissatisfaction at the state of records in Haskell. Answers remained vague, which gives us a mood indicator more than an action plan, but there are notable requests for &lt;code&gt;OverloadedRecordDot&lt;&#x2F;code&gt; and &lt;code&gt;NoFieldSelectors&lt;&#x2F;code&gt; to be enabled by default.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;tooling&quot;&gt;Tooling&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;compilation-times&quot;&gt;Compilation times&lt;&#x2F;h4&gt;
&lt;p&gt;“Make GHC 10x faster” is one of the prevalent moods amongst participants. Coming from end-users, these suggestions do not come with actionable advice or specific pain points in the compilation pipeline. Specific situations like underpowered laptops have been mentioned.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ecosystem-consolidation-developer-experience&quot;&gt;Ecosystem consolidation, Developer Experience&lt;&#x2F;h4&gt;
&lt;p&gt;As the Haskell language and its ecosystem are more than thirty years old, it’s normal that we see a certain degree of fragmentation in the tooling, as new people and ideas enter the community. However, they tend to also confuse newcomers, who do not wish to navigate the intricacies of the technical and political disagreements that produced the existing tooling ecosystem. Survey participants very helpfully directed our attention to projects like &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.astral.sh&#x2F;uv&#x2F;&quot;&gt;&lt;code&gt;uv&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;  for an example of a fully-integrated toolchain (tooling management and project management).&lt;&#x2F;p&gt;
&lt;p&gt;Compilation error messages are also called confusing. This might be explained by a mismatch between the intended audience of those messages and the actual audience. Error messages may feel more intended for implementers than for end-users. Recent versions of GHC have started to direct users to the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;errors.haskell.org&#x2F;&quot;&gt;Haskell error index&lt;&#x2F;a&gt;, so we can expect some positive development in the coming years. However, we should keep improving error messages for end-users who are not in the know about implementation details.&lt;&#x2F;p&gt;
&lt;p&gt;Lastly, the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;haskell-language-server.readthedocs.io&#x2F;en&#x2F;latest&#x2F;what-is-hls.html#what-is-the-haskell-language-server&quot;&gt;Haskell Language Server&lt;&#x2F;a&gt; is often mentioned when participants complain about development experience, mostly due to the fact that they have to directly interact with it. Memory usage and reliability are thus felt by most people.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;program-performance&quot;&gt;Program Performance&lt;&#x2F;h3&gt;
&lt;h4 id=&quot;pervasive-laziness&quot;&gt;Pervasive Laziness&lt;&#x2F;h4&gt;
&lt;p&gt;Anxiety about laziness is a recurring theme, indicating lasting difficulties developers have in reasoning about lazy evaluation. Resource blow-up due to misunderstood runtime behaviours make people anxious when it comes to deploying or distributing Haskell software. Actionable advice like eliminating lazy I&#x2F;O was given, which is indeed known for tripping people up, when they may expect it to happen only when they use a streaming library.&lt;&#x2F;p&gt;
&lt;p&gt;Some suggestions are more targeted, like having data structures strict by default, and only explicitly lazy when the developer wishes so, by means of the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ghc.gitlab.haskell.org&#x2F;ghc&#x2F;doc&#x2F;users_guide&#x2F;exts&#x2F;strict.html#strict-by-default-data-types&quot;&gt;StrictData extension&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;minor-topics&quot;&gt;Minor Topics&lt;&#x2F;h2&gt;
&lt;p&gt;These topics have been mentioned in lesser but still substantial amounts. This does not mean that they are worthless, just that other concerns seemed more pressing to most participants.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;dependent-haskell&quot;&gt;Dependent Haskell&lt;&#x2F;h3&gt;
&lt;p&gt;When people are not thinking about what is bad, they start thinking about what could be better. The subject of Dependent Types has come up enough times to attract our attention. Dependent Haskell is viewed as a unified solution that can replace otherwise loosely related language extensions. You can see the progress of the implementation on the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ghc.serokell.io&#x2F;dh&quot;&gt;Dependent Haskell Roadmap&lt;&#x2F;a&gt; page.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;community&quot;&gt;Community&lt;&#x2F;h3&gt;
&lt;p&gt;The desire for community is ever present in human beings, and Haskellers are no exception. They want to see their community thrive, meet more people, and see higher regard for non-technical contributions.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;effect-systems&quot;&gt;Effect Systems&lt;&#x2F;h3&gt;
&lt;p&gt;The past few years have seen an increase in the research and development of algebraic effect systems, meant to replace the shortcomings of the existing Transformers&#x2F;MTL approaches. We are beginning to see an interest for &lt;code&gt;base&lt;&#x2F;code&gt; to provide such a thing out of the box. Participants wishing for more interoperability also expressed the desire to change most functions operating in the &lt;code&gt;IO&lt;&#x2F;code&gt; monad to instead make use of &lt;code&gt;MonadIO&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Participants expressed interest in having a coherent story when it comes to the architecture of Haskell programs, the choice of libraries, and the fragmentation of the ecosystem.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;debugging&quot;&gt;Debugging&lt;&#x2F;h3&gt;
&lt;p&gt;Debugging is still a sore point for participants, but the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;well-typed.github.io&#x2F;haskell-debugger&#x2F;&quot;&gt;Haskell Debugger&lt;&#x2F;a&gt; is mentioned and seems to address most of the pain points. “&lt;code&gt;HasCallStack&lt;&#x2F;code&gt; everywhere&quot; is also mentioned, which we consider to be a symptom of the perception of the existing debugging tools and backtrace mechanisms.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;academia-industry-hobbyists&quot;&gt;Academia, Industry, Hobbyists&lt;&#x2F;h2&gt;
&lt;p&gt;An underlying theme of many of the previously mentioned answers is the question of “To whom does Haskell cater?”. Historically an academic project to study non-strict evaluation and pure functional programming, Haskell has grown to be adopted by software engineers in industry, leading to different ways of learning and socialising. A certain proportion of industrial programmers feel disenfranchised by the academic focus of the ecosystem, especially those that do not have an academic background or formal education.&lt;&#x2F;p&gt;
&lt;p&gt;One answer in particular elaborates on the subject:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;There needs to be a recognition that there is documentation produced for&#x2F;by the type theory &#x2F; heavy mathematics enthusiasts which doesn&#x27;t translate well as developer documentation. When new developers are looking for best practices they are often pointed into the direction of blogs written by clever, well-meaning people who write in a verbose style, often from the vantage point of mathematical abstractions. Any documentation that is software engineering oriented is always welcomed, but surprisingly rare.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;special-mentions&quot;&gt;Special Mentions&lt;&#x2F;h2&gt;
&lt;p&gt;Some answers had to be categorised in a way that had to reflect how incredible they were. Here are our favourites:&lt;&#x2F;p&gt;
&lt;h4 id=&quot;a-whole-other-language&quot;&gt;&quot;A whole other language&quot;&lt;&#x2F;h4&gt;
&lt;p&gt;Some survey participants just want another language, going beyond taking inspiration from others, with suggestions such as swapping the meanings of &lt;code&gt;::&lt;&#x2F;code&gt; and &lt;code&gt;:&lt;&#x2F;code&gt;, removing laziness, or a total language and tooling cleanup, scorched-earth style. &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;lean-lang.org&#x2F;&quot;&gt;Lean&lt;&#x2F;a&gt; and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.idris-lang.org&#x2F;&quot;&gt;Idris&lt;&#x2F;a&gt; might be more up your alley.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;i-don-t-know-how-but-make-it-better&quot;&gt;&quot;I don’t know how, but make it better&quot;&lt;&#x2F;h4&gt;
&lt;p&gt;The vague rants and self-admitted impossible ideas hold a special place in our heart. “Replace base”, “tooling like TypeScript”, and “fix the weird syntax” were pretty good. It&#x27;s hard to imagine these changes being possible without the level of support given to large corporate projects, such as VS Code, TypeScript or Go.&lt;&#x2F;p&gt;
&lt;p&gt;Special mention for “weird syntax”, which we can &lt;strong&gt;absolutely&lt;&#x2F;strong&gt; &lt;strong&gt;do&lt;&#x2F;strong&gt; something about, we just forgot to press the big red button that says “fix syntax”. Thanks for the reminder!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;to-conclude&quot;&gt;To conclude&lt;&#x2F;h2&gt;
&lt;p&gt;This year’s survey has brought us a very valuable insight into the preoccupations of a certain proportion of Haskellers. Of course, the results are to be handled with care, as surveys are a self-selecting tool in the first place. We cannot possibly know what exactly the community thinks, only what the subset that answers the survey thinks.
We had a variety of answers, ranging from one word (“base”) to several paragraphs that could be the start of a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ghc-proposals&#x2F;ghc-proposals&#x2F;?tab=readme-ov-file#ghc-proposals&quot;&gt;GHC Proposal&lt;&#x2F;a&gt; or a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;core-libraries-committee?tab=readme-ov-file#core-libraries-committee&quot;&gt;Core Libraries Proposal&lt;&#x2F;a&gt;. Anyone can open one, and we encourage you to do so!&lt;&#x2F;p&gt;
&lt;p&gt;Tooling is a common thread to many topics, from beginner onboarding to consolidation of the developer experience. There is a tension between a fully integrated development ecosystem and having a language with multiple implementations that are free to explore different paths.&lt;&#x2F;p&gt;
&lt;p&gt;Thank you again for participating in this year’s survey. Each new edition gives us more data points to measure the community trends, and they help the various groups with crafting the best experience for Haskell developers.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>A Couple Million Lines of Haskell: Production Engineering at Mercury</title>
        <published>2026-03-30T00:00:00+00:00</published>
        <updated>2026-03-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/a-couple-million-lines-of-haskell/"/>
        <id>https://blog.haskell.org/a-couple-million-lines-of-haskell/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/a-couple-million-lines-of-haskell/">&lt;p&gt;&lt;em&gt;The editors of the Haskell Blog are happy to announce a new series of articles called &quot;&lt;a href=&quot;&#x2F;categories&#x2F;haskellers-from-the-trenches&#x2F;&quot;&gt;Haskellers from the trenches&lt;&#x2F;a&gt;&quot;,
where we invite experienced engineers to talk about their subjects of expertise, best practices, and production tales.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Engineering rigour and artistic creativity are a fantastic combination, and this series aims to be the synthesis of these two aspects within the Haskell world.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;I first heard about Haskell when I was sixteen, sitting in a high school computer science class where we were writing Java and learning, among other things, that &lt;code&gt;NullPointerException&lt;&#x2F;code&gt; was apparently a lifestyle choice if you decided to go into software development. While looking at the &lt;code&gt;&#x2F;r&#x2F;programming&lt;&#x2F;code&gt; subreddit after school, I stumbled across a reference to a language where null pointer exceptions simply could not happen, where the type system could prevent an entire category of bugs that I had been fighting with every week. Haskell. I was immediately, hopelessly enamored with the idea.&lt;&#x2F;p&gt;
&lt;p&gt;I have been writing Haskell for nearly two decades now, and I still think the value proposition I fell in love with at sixteen was basically right. What took me longer to learn is what that promise looks like after a codebase gets large, the company grows faster than its documentation, and the system is allowed to touch money. Haskell earns its keep there in numerous, sturdy ways. It lets you pack operational knowledge into APIs, put dangerous machinery behind tight boundaries, and make the safe path the easy one. At a growing company, those aren&#x27;t just matters of taste; they are how you keep a system understandable after the people who first understood it have moved on.&lt;&#x2F;p&gt;
&lt;p&gt;Fast forward to today: I work at Mercury, a fintech company that provides banking services.* We serve over 300,000 businesses. We processed $248 billion in transaction volume in 2025 on $650 million in annualized revenue, and are, at the time of writing, in the process of obtaining a national bank charter in the USA from the OCC. We have around 1,500 employees. Our engineering organization largely hires generalists, and most of them have never written a line of Haskell before joining.&lt;&#x2F;p&gt;
&lt;p&gt;My time working at Mercury has changed how I think about the language more than any sermon about purity ever did. Elegance is pleasant, but keeping your business alive is compulsory.&lt;&#x2F;p&gt;
&lt;p&gt;Our codebase is roughly 2 million lines of Haskell, once you strip out comments and such.&lt;&#x2F;p&gt;
&lt;p&gt;This is the part where you are supposed to recoil in horror.&lt;&#x2F;p&gt;
&lt;p&gt;A couple million lines of Haskell, maintained by people who learned the language on the job, at a company that moves huge amounts of money? The conventional wisdom says this should be a disaster, but surprisingly, it isn&#x27;t. The system we&#x27;ve built has worked well for years, through hypergrowth, through the SVB crisis that sent $2 billion in new deposits our way in five days,&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#svb&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; through regulatory examinations, through all the ordinary and extraordinary things that happen to a financial system at scale.&lt;&#x2F;p&gt;
&lt;p&gt;This article is about why it works. Not in the &quot;Haskell is beautiful&quot; sense, though it is. Not in the &quot;the compiler will save us from ourselves&quot; sense, though I frequently feel gratitude in that direction. I mean in the much less romantic and much more useful sense that we run this language in production, at scale, with a rapidly changing team, and have learned some hard lessons about what it takes to keep the whole enterprise afloat. The beauty of Haskell is charming enough, but there is a whole swath of operational and organizational reality beyond it, and if you ignore that reality for too long, your company will likely fire the whole Haskell team&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#zalora&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; and start writing PHP or something instead.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-we-think-about-reliability&quot;&gt;How We Think About Reliability&lt;&#x2F;h2&gt;
&lt;p&gt;Before diving into practical advice, a note on philosophy.&lt;&#x2F;p&gt;
&lt;p&gt;There is a traditional way of thinking about system reliability that focuses on preventing failures. You enumerate the things that can go wrong. You add checks. You write tests for each bad case. You hunt for bugs. This is, of course, necessary work, and we do it. But it is not sufficient, and if you orient entirely around it you develop a specific blind spot: you get very good at cataloguing the ways things break and very bad at understanding why they ordinarily work.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#safety-one&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We try to think about it differently. A system operates reliably because it can &lt;strong&gt;absorb variation&lt;&#x2F;strong&gt;: it degrades gracefully, its operators can understand and adjust it, and the architecture makes the right thing easy and the wrong thing difficult.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#safety-two&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; Reliability is not just the absence of failure. It is the presence of adaptive capacity. It is a system&#x27;s ability to keep functioning while reality continues its longstanding and regrettable habit of refusing to hold still.&lt;&#x2F;p&gt;
&lt;p&gt;When you have hundreds of engineers working in a multi-million-line codebase, many of whom are six months into their Haskell careers, &quot;adaptive capacity&quot; stops being a nifty phrase from a resilience engineering paper and starts being a daily concern. Patrick McKenzie has observed that in a company growing at 2x per year, half of your coworkers will always have less than a year of experience. A year later, half of your coworkers will still have less than a year of experience. For very successful companies, this never stops being true.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#patio11&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; You become organizationally ancient very quickly, whether you like it or not, and the things you know become institutional dark matter: load-bearing, but invisible to most of the people around you.&lt;&#x2F;p&gt;
&lt;p&gt;So the questions we ask are operational. Can the new hire on your team read this module and understand what it does? If the database is slow, does this service degrade or does it fall over and take its neighbors with it? If someone misuses an interface, does the compiler tell them, or do we find out when the on-call gets paged? If you don&#x27;t have answers to those questions, you have a future incident quietly unfolding.&lt;&#x2F;p&gt;
&lt;p&gt;This is why I increasingly think of the type system as an &lt;strong&gt;operational aid&lt;&#x2F;strong&gt; more than a correctness proof. Its value is not merely that it rules out certain classes of errors, though it does. Its value is that it encodes institutional knowledge in a form that survives the departure of the person who wrote it. In a fast-growing company, people leave, people transfer teams, people go on vacation or parental leave, people join, and the churn means that things people knew walk out the door with them unless you have written them down somewhere. Ideally, you have written them down in a form that the compiler can read, because the compiler is much more disciplined than the average wiki page.&lt;&#x2F;p&gt;
&lt;p&gt;This extends beyond code. As a member of our stability engineering team, we constantly investigate the prospective production behavior of features and products. We do not do this to slow down product development, but in partnership with the team shipping the feature, to make sure we are prepared to deal with the fallout when it breaks and, if possible, to make that fallout boring rather than exciting. We ask things like: what is the blast radius if this fails? Which operations must be idempotent, and how? What does the rollback look like? What happens to in-flight work? Which systems will absorb the failure, and which ones will amplify it? The point is to have the conversation early enough that it shapes the design rather than merely auditing the launch after all the important decisions have already become expensive to revisit.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#prr&quot;&gt;6&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Our philosophy, stated plainly: we are not the quality police. We are the people who would like to help you avoid being woken up at 4 AM to deal with the fallout of a broken feature. Rather than a deeply ideological stance, we simply desire to help people.&lt;&#x2F;p&gt;
&lt;p&gt;So, in light of that, how do we make Haskell work in production?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;purity-is-a-boundary-not-a-property&quot;&gt;Purity Is a Boundary, Not a Property&lt;&#x2F;h2&gt;
&lt;p&gt;My hot take: the first and most consequential misunderstanding about Haskell is that purity is not something the language &lt;em&gt;is&lt;&#x2F;em&gt;, so much as that it is something your interfaces enforce.&lt;&#x2F;p&gt;
&lt;p&gt;Under the hood, Haskell is not a magical machine that performs side effects despite being pure. Behind every &quot;pure&quot; function in &lt;code&gt;bytestring&lt;&#x2F;code&gt;, &lt;code&gt;text&lt;&#x2F;code&gt;, and &lt;code&gt;vector&lt;&#x2F;code&gt; lies a cheerful little hellscape of mutable allocation, buffer writes, unsafe coercions, and other behavior that would alarm you if you discovered it in a junior engineer&#x27;s side project. Behind the &lt;code&gt;ST&lt;&#x2F;code&gt; monad lies in-place mutation and side effects, observable within the computation. What makes it acceptable is that the side effects are encapsulated such that the boundary cannot be violated.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;runST&lt;&#x2F;span&gt;&lt;span&gt; :: (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;forall&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; s&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; ST&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; s a&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The rank-2 type (that is, the type &lt;code&gt;s&lt;&#x2F;code&gt; is scoped within the parenthesis and can&#x27;t escape) of &lt;code&gt;runST&lt;&#x2F;code&gt; ensures that the mutable references created inside the computation cannot escape due to being tagged with the type &lt;code&gt;s&lt;&#x2F;code&gt;. Internally, all sorts of imperative nonsense may occur. Externally, the function is pure. The world outside the boundary gets none of the mutation, only the result.&lt;&#x2F;p&gt;
&lt;p&gt;This is, I think, a wonderful design principle in the larger scheme of things when writ large: you can permit arbitrarily dangerous operations within a scope, provided the scope&#x27;s exit is typed narrowly enough that the danger cannot leak. That principle applies everywhere in production. Your database layer uses connection pooling, retry logic, and mutable state internally. Your cache uses concurrent mutable maps. Your HTTP client probably has circuit breakers, pooled connections, and a small municipal government&#x27;s worth of bookkeeping. None of this is a problem if the interface is tight enough to prevent misuse and the boundary holds.&lt;&#x2F;p&gt;
&lt;p&gt;In production, the goal is often not to avoid mutation entirely, because that is not a serious proposition for most real systems. The goal is to contain mutation, make the containment legible, and verify that it stays contained. Often the right question is not &quot;is this pure?&quot; but &quot;where is the impurity, and how much of the codebase is allowed to know about it?&quot;&lt;&#x2F;p&gt;
&lt;p&gt;For a new engineer who learned Haskell three months ago, &quot;purity is a boundary you try to maintain&quot; is much more useful than &quot;Haskell is pure.&quot; One tells them what to do when they sit down to design a module. The other mostly sits there looking profound.&lt;&#x2F;p&gt;
&lt;p&gt;This boundary-oriented view of purity sets up a more general pattern that recurs throughout production engineering in Haskell: dangerous things are tolerable when they are fenced in, carefully exposed, and hard to misuse. That is true of mutation. It is true of retries, transactions, state machines, distributed workflows, and type-level machinery. Much of what follows is really just this same idea, wearing different hats.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;make-the-right-thing-easy&quot;&gt;Make the Right Thing Easy&lt;&#x2F;h2&gt;
&lt;p&gt;There is a pattern in large codebases where correctness depends on performing operations in a particular order, or including a particular step that has no visible connection to the main work.&lt;&#x2F;p&gt;
&lt;p&gt;&quot;Remember to flush the audit log after every transaction.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;&quot;Always check the feature flag before calling this endpoint.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;&quot;Make sure to enqueue the notification &lt;em&gt;inside&lt;&#x2F;em&gt; the database transaction, not after it.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;These are the incantations of operational lore. They live in wiki pages, onboarding documents, half-forgotten design reviews, and the memories of senior engineers who are now three teams away and booked solid until Thursday. In a company that is hiring aggressively, the half-life of tribal knowledge is alarmingly short. When an engineer leaves, the incantations fade. When a deadline approaches, they are the first thing skipped. When a new engineer joins, they often have no way to know the incantation exists at all. Nothing says &quot;robust system design&quot; quite like a critical invariant living in a Slack thread from nine months ago&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#slack&quot;&gt;7&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Haskell gives you tools to encode these incantations in types so they cannot be forgotten. This is, for my money, the single most valuable thing the language offers a production engineering organization.&lt;&#x2F;p&gt;
&lt;p&gt;Consider a simplified version of a real pattern: you need to ensure that certain side effects (sending a notification, publishing an event) happen &lt;em&gt;transactionally&lt;&#x2F;em&gt; with a database write. Not before, not after, and not in a separate transaction. Together, or not at all.&lt;&#x2F;p&gt;
&lt;p&gt;The naïve approach is to tell people to use the right function:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt;-- Please use this one, not the other one&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;writeWithEvents&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Transaction&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt;] -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt;-- Don&amp;#39;t use this directly (but we can&amp;#39;t stop you)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;writeTransaction&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Transaction&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;publishEvents&lt;&#x2F;span&gt;&lt;span&gt; :: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Event&lt;&#x2F;span&gt;&lt;span&gt;] -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is rookie-level engineering. It works until it doesn&#x27;t, and &quot;until it doesn&#x27;t&quot; tends to arrive on a Friday afternoon when the person who wrote the wiki page is on vacation and everybody else is discovering, in real time, that the wiki page was load-bearing.&lt;&#x2F;p&gt;
&lt;p&gt;A better approach restructures the types so that the only way to &lt;em&gt;commit&lt;&#x2F;em&gt; work is through a path that includes event publication:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;data Transact&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt; -- opaque; cannot be run directly&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;record&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Transaction&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Transact&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;emit&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Event&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Transact&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt;-- The *only* way to execute a Transact: commit and publish atomically&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;commit&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Transact&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; IO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now the incantation is the only door in the room. You cannot forget it because there is nothing else to do. The type system has not proven anything especially deep about your events. It has done something more practical: it has made the correct operational procedure the path of least resistance.&lt;&#x2F;p&gt;
&lt;p&gt;That distinction matters. In production, there are plenty of places where we do not need a theorem. We need a design that makes it difficult for an ordinary busy engineer to accidentally do the wrong thing while trying to do a dozen other perfectly reasonable things. The compiler is not merely checking logic here; it is preserving institutional memory and turning it into a hard-edged interface.&lt;&#x2F;p&gt;
&lt;p&gt;When a new engineer joins and asks &quot;how do I write a transaction?&quot;, the type system answers them. When a senior engineer leaves, the answer remains. The institutional knowledge survived not because someone documented it beautifully, though documentation is pleasant when available, but because someone encoded it in a form the compiler enforces. Again, the compiler is a better custodian of operational lore than the average wiki and less prone to people forgetting to update it as the reality of the system changes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;durable-execution&quot;&gt;Durable Execution&lt;&#x2F;h2&gt;
&lt;p&gt;The pattern above — structuring types so that the correct operational procedure is the only procedure — works well within a single transaction. Financial systems, unfortunately, have never felt obligated to remain inside a single transaction.&lt;&#x2F;p&gt;
&lt;p&gt;They are full of processes that span multiple steps, multiple services, and multiple failure modes. Send a payment, wait for a partner to acknowledge it, update the ledger, notify the customer, handle cancellation, handle timeout, handle the case where the partner said yes but your worker died before recording the answer, handle the case where the partner said nothing because the network briefly entered a higher plane of existence and declined to tell you about it. If any step fails, you need to know where you were, what has already happened, and what still needs to happen. You need state. You need retries. You need timeouts. You need idempotence. You need all of these things to keep working across process crashes and deployments. Very quickly, what began as &quot;just some business logic&quot; amasses a remarkable amount of one-off repeats of common operational concerns.&lt;&#x2F;p&gt;
&lt;p&gt;Previously at Mercury, we coordinated these processes with database-backed state machines driven by cron jobs and background workers, with retry logic and timeout handling scattered across the codebase. It worked. It also required the sort of vigilance usually associated with defusing unexploded ordnance. It was fragile, difficult to reason about, and the source of a disproportionate share of our operational incidents.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;temporal.io&quot;&gt;Temporal&lt;&#x2F;a&gt; is our durable execution framework, and adopting it was one of the better infrastructure decisions we have made. You write your workflow as ordinary sequential code, and the platform records every step in an event history. If a worker crashes mid-workflow, another worker replays the deterministic prefix to reconstruct the state, then continues from where it left off. Retries, timeouts, cancellation, and error handling are provided by the platform rather than each team reimplementing them poorly.&lt;&#x2F;p&gt;
&lt;p&gt;I think of Temporal as Frankenstein&#x27;s monster, in the flattering sense: assembled from excellent parts, animated by improbable effort, and smarter than many of the people alarmed by it. It takes durable history, replay, and determinism (things some platforms get natively,) and bolts them onto runtimes that were never born knowing how to do any of this. Most of us are not going to rewrite our companies in Erlang. Temporal is a prosthetic for the rest of us. It gives ordinary languages a shot at some of the same operational virtues by slightly mad but highly effective means.&lt;&#x2F;p&gt;
&lt;p&gt;The alignment with Haskell dovetails nicely with the virtues often attributed to Haskell: A Temporal workflow is, in an important sense, a pure function over its event history. Temporal Workflows have a determinism requirement — that a replayed workflow must produce the same sequence of commands as the original — which is exactly the same constraint Haskell imposes on pure code: same inputs, same outputs. Side effects are isolated into &lt;em&gt;activities&lt;&#x2F;em&gt;, which are the workflow&#x27;s equivalent of &lt;code&gt;IO&lt;&#x2F;code&gt;. The workflow orchestrates; the activities execute. If you have spent any time thinking about pure core &#x2F; impure shell, this is that model with the platform enforcing the separation rather than relying on sheer discipline.&lt;&#x2F;p&gt;
&lt;p&gt;We built and open-sourced &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;MercuryTechnologies&#x2F;hs-temporal-sdk&quot;&gt;&lt;code&gt;hs-temporal-sdk&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, our Haskell SDK for Temporal, which wraps the official Core SDK (Rust, via FFI) and provides a Haskell-native API for defining workflows, activities, and workers.&lt;&#x2F;p&gt;
&lt;p&gt;I gave a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=FdGMaLcEP2M&quot;&gt;talk about our adoption patterns at Temporal&#x27;s Replay conference&lt;&#x2F;a&gt;, and the short version is: Temporal has let us replace fragile chains of cron jobs and database-backed state machines with durable workflows where the platform handles the coordination. The operational improvement has been substantial. It is difficult to overstate how pleasant it is to delete a hand-rolled distributed state machine and replace it with something whose failure semantics were not improvised during sprint planning.&lt;&#x2F;p&gt;
&lt;p&gt;This, again, is adaptive capacity in a different costume. A system that can survive worker crashes, process restarts, and long-lived coordination without losing its place is a system whose operators have more leverage and fewer mysteries to unwind during an incident.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;design-for-your-domain-not-your-transport&quot;&gt;Design for Your Domain, Not Your Transport&lt;&#x2F;h2&gt;
&lt;p&gt;As your production system grows, a common mistake I&#x27;ve observed is letting the invoking system leak into the domain model.&lt;&#x2F;p&gt;
&lt;p&gt;We have code that throws HTTP status code exceptions which return those results directly to the user on the frontend. This made sense when the code was written, because it ran in an HTTP request handler. Then, as happens in any growing codebase, pieces of that code got extracted and reused. Now it also runs in cron jobs. It runs in queued background workers. It runs in Temporal workflows. And it still throws &lt;code&gt;StatusCodeException 409 &quot;Conflict&quot;&lt;&#x2F;code&gt; when something goes wrong, which is an absolutely unhinged thing for a cron job to do. A cron job does not have a caller waiting for a 409. Nobody is reading that status code. The error has propagated through the system simply because the original abstraction was coupled to its transport layer.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is conceptually simple: model your domain errors as domain types. A payment that fails because of insufficient funds should be an &lt;code&gt;InsufficientFunds&lt;&#x2F;code&gt;, not a 402. A duplicate request should be a &lt;code&gt;DuplicateRequest&lt;&#x2F;code&gt;, not a 409. These are things your business logic can match on, retry against, log meaningfully, and handle differently depending on context.&lt;&#x2F;p&gt;
&lt;p&gt;Then you write thin translation layers at each boundary:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;data PaymentError&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; InsufficientFunds&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; DuplicateRequest&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; RequestId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; PartnerTimeout&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Partner&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;toHttpError&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; PaymentError&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; HttpResponse&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toHttpError &lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt;InsufficientFunds&lt;&#x2F;span&gt;&lt;span&gt;       = err402 &lt;&#x2F;span&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;&amp;quot;Insufficient funds&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toHttpError (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt;DuplicateRequest&lt;&#x2F;span&gt;&lt;span&gt; _)    = err409 &lt;&#x2F;span&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;&amp;quot;Duplicate request&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toHttpError (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt;PartnerTimeout&lt;&#x2F;span&gt;&lt;span&gt; _)      = err502 &lt;&#x2F;span&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;&amp;quot;Partner unavailable&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;toWorkerStrategy&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; PaymentError&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; WorkerAction&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toWorkerStrategy &lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt;InsufficientFunds&lt;&#x2F;span&gt;&lt;span&gt;    =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; Fail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt; &amp;quot;Insufficient funds&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toWorkerStrategy (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt;DuplicateRequest&lt;&#x2F;span&gt;&lt;span&gt; _) =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; Skip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;toWorkerStrategy (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt;PartnerTimeout&lt;&#x2F;span&gt;&lt;span&gt; _)   =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; RetryWithBackoff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nothing novel about it. But it is remarkable how often it gets skipped in practice, because the first version of any code is written for &lt;em&gt;one&lt;&#x2F;em&gt; context, and by the time you realize it will be called from three others, the status code exceptions are load-bearing because someone decided to catch those exceptions as part of business logic and nobody wants to refactor them.&lt;&#x2F;p&gt;
&lt;p&gt;The earlier you make this separation, the less it costs. The later you make it, the stranger the resulting behavior becomes. Eventually you wind up with cron jobs hurling 409s at Sentry and background workers interpreting HTTP-specific exceptions as business semantics, which is how abstractions let you know they have escaped containment.&lt;&#x2F;p&gt;
&lt;p&gt;This is the same principle as purity, expressed in domain language rather than operational language. Your transport concerns belong at the edges. Your domain model should survive being invoked from a web handler, a CLI, a cron job, a background worker, or a workflow engine without having to drag an HTTP status code behind it like a tin can tied to a wedding car.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-type-encoding-tradeoff&quot;&gt;The Type Encoding Tradeoff&lt;&#x2F;h2&gt;
&lt;p&gt;Here is the part where I tell you not to do too much of the thing I just told you to do.&lt;&#x2F;p&gt;
&lt;p&gt;Encoding invariants into types is powerful. It is also expensive. Not at runtime, but in cognitive overhead, in the rigidity it introduces, and in the difficulty of changing things later when the requirements shift. And the requirements &lt;em&gt;will&lt;&#x2F;em&gt; shift. If you work at a company where they do not, I would like to know your secret, and also your stock ticker.&lt;&#x2F;p&gt;
&lt;p&gt;Every invariant you push into the type system is a constraint on every future engineer who touches that code. If violating the constraint would cause data loss, financial errors, regulatory trouble, or a poor soul&#x27;s pager to go off, then the cost is justified. If the constraint is &quot;we currently happen to do things this way,&quot; or &quot;I read this article about dependent types and I &lt;em&gt;simply must&lt;&#x2F;em&gt; apply that to my authorization logic,&quot; you have likely just made your codebase harder to change for no operational benefit. The next person to encounter it will either spend a week refactoring the types or, more likely, find a way around them that is worse than what you were trying to prevent.&lt;&#x2F;p&gt;
&lt;p&gt;There is a spectrum, and production codebases must live on it honestly.&lt;&#x2F;p&gt;
&lt;p&gt;At one end: you encode everything. Your types are a faithful model of your domain. Illegal states are unrepresentable. Refactoring takes weeks because changing a business rule means threading a type change through fifty modules. New engineers stare at the type signatures and wonder what they have done to deserve this, then quietly begin discussing their career options with a therapist. You have built a cathedral. Cathedrals are beautiful. They are also expensive, cold, and not especially famous for how quickly one renovates the plumbing.&lt;&#x2F;p&gt;
&lt;p&gt;At the other end: you encode nothing. Your types are &lt;code&gt;String&lt;&#x2F;code&gt; and &lt;code&gt;IO ()&lt;&#x2F;code&gt; and, in the worst case, &lt;code&gt;Dynamic&lt;&#x2F;code&gt;. The code is easy to change because there are no contracts to violate. The system works because the people who built it are still around and remember what the strings mean. When they leave, it stops working, and nobody knows why. You have built a tent. Tents are flexible, portable, and, under certain weather conditions, a very direct way to learn about the sky. This is, of course, one of the reasons many Haskell developers flee to Haskell in the first place — to avoid the suffering that comes from this approach.&lt;&#x2F;p&gt;
&lt;p&gt;The sweet spot is somewhere in the middle. A few heuristics I think are useful:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Encode invariants that protect against silent corruption.&lt;&#x2F;strong&gt; If a violation would produce wrong data without any immediate error (a transaction committed without its events, a payment processed without an audit log, a state transition that looks plausible but is semantically impossible), put it in the types. The feedback loop for silent failures is too long to rely on human diligence.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Use runtime checks for invariants that fail loudly.&lt;&#x2F;strong&gt; If a violation would produce an immediate, obvious error (a 500 response, a failed assertion, a type mismatch at a JSON boundary), a runtime check with a good error message may be enough. You will catch it before production, or very quickly after.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Resist the urge to model your entire domain in types.&lt;&#x2F;strong&gt; Your domain is messy. It has edge cases, grandfather clauses, rules that contradict each other, and special behavior for three specific customers that dates back to 2018 and that nobody fully understands. The type system wants crispness. Your business does not provide it. Nor will it ever.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Remember that types are for the team, not just for the compiler.&lt;&#x2F;strong&gt; The compiler is one tool among many. Tests, documentation, code review, examples, playbooks: these all combine to provide defense in depth. The goal is not to win an argument with the type checker. The goal is to build a system that a team of humans, including humans who learned Haskell this year, can operate, extend, and maintain.&lt;&#x2F;p&gt;
&lt;p&gt;That said, intense type-level machinery is sometimes exactly what you need. We have internal libraries where the types are genuinely hairy: GADTs, type families, phantom types tracking state transitions. These tend to be mechanisms where getting it wrong means money goes to the wrong place or a regulatory invariant is violated. The complexity is absolutely essential here.&lt;&#x2F;p&gt;
&lt;p&gt;The key thing, if you want to do this sustainably, is that we &lt;em&gt;encapsulate&lt;&#x2F;em&gt; the complexity. The module that implements the type-level state machine typically has a small number of authors who understand it deeply and, ideally, a thorough test suite. The module that &lt;em&gt;uses&lt;&#x2F;em&gt; it has a surface API that looks like five normal functions with normal types. A product engineer on another team can call those functions without knowing or caring that underneath there is a small type-level theorem prover ensuring they cannot commit a transaction in the wrong state. The proof obligations are discharged inside the boundary, not leaked across it.&lt;&#x2F;p&gt;
&lt;p&gt;This is the same containment principle as purity, applied one level up. The complexity itself is fine, because it buys you something valuable. What causes problems is complexity that leaks across module boundaries into code maintained by people who did not sign up for it. We catch this in code review more than anywhere else. Someone opens a PR that touches a module they do not own, and the diff is full of type annotations they had to cargo-cult from a neighboring file just to make the compiler stop screaming. That is usually a sign the abstraction has become a form of friendly fire.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;designing-for-introspection&quot;&gt;Designing for Introspection&lt;&#x2F;h2&gt;
&lt;p&gt;If reliability is about adaptive capacity, introspection is one of the ways you buy it. Operators cannot understand what they cannot see. Teams cannot adapt systems whose internals are opaque. Observability is not a garnish you sprinkle on at the end. It is part of the design surface of the software.&lt;&#x2F;p&gt;
&lt;p&gt;This matters a great deal in Haskell, because Haskell does not have monkey patching. You cannot, at runtime, reach into a library and replace its HTTP client with one that records timings, or swap its database calls for ones that emit OpenTelemetry spans. This is not unique to Haskell, of course. Rust has the same fundamental constraint: no monkey patching, no runtime method swizzling, no language-level &quot;let me just interpose here real quick&quot; escape hatch. The orphan rule even prevents you from adding trait implementations for types you do not own. The difference is that the Rust ecosystem has largely converged on the &lt;code&gt;tower&lt;&#x2F;code&gt; middleware pattern as the answer, while Haskell&#x27;s ecosystem is still fragmented across several approaches. The constraint is the same; the question is whether your ecosystem gives you a conventional escape hatch or leaves every team to improvise one.&lt;&#x2F;p&gt;
&lt;p&gt;So, if a library exposes its functionality as a set of concrete top-level functions, your options for instrumenting it are limited to wrapping those functions in a new module and hoping nobody forgets to import yours instead of the original. Hope, it should be said, is not one of the stronger architectural patterns.&lt;&#x2F;p&gt;
&lt;p&gt;A point generalizeable for any language: &lt;strong&gt;&lt;em&gt;You cannot operate what you cannot see.&lt;&#x2F;em&gt;&lt;&#x2F;strong&gt; If your traces have large opaque gaps because a library does something expensive and provides no hooks for you to measure it, you will find out what is slow when a customer complains, not when your dashboards tell you. If the dashboards tell you because the customer has already complained, then you are already paying interest on that design decision.&lt;&#x2F;p&gt;
&lt;p&gt;The solution I reach for most often is &lt;strong&gt;records of functions&lt;&#x2F;strong&gt;. Instead of exposing a module full of concrete functions, you expose a record whose fields are the functions. The caller can then wrap, instrument, mock, or replace any individual function without touching the rest. I wrote about this at length in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.iankduncan.com&#x2F;articles&#x2F;2024-01-26-records-of-effects&quot;&gt;Embracing Flexibility in Haskell Libraries&lt;&#x2F;a&gt;, but the short version is:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt;-- A concrete module gives you no leverage:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;sendRequest&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Request&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; IO Response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt;-- A record of functions gives you all of it:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;data HttpClient&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; HttpClient&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; sendRequest&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Request&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; IO Response&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; getManager&lt;&#x2F;span&gt;&lt;span&gt;  ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; IO Manager&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With the record, you can wrap &lt;code&gt;sendRequest&lt;&#x2F;code&gt; with timing instrumentation and return a new &lt;code&gt;HttpClient&lt;&#x2F;code&gt;. You can inject faults for testing. You can swap the implementation for a mock. You can add retries, tracing, request rewriting, tenant-specific behavior, or whatever other cross-cutting concern production has discovered for you this quarter. All at runtime, without touching the library&#x27;s source code. WAI got this right with &lt;code&gt;type Middleware = Application -&amp;gt; Application&lt;&#x2F;code&gt;: composable transformation of behavior is massively useful across a wide variety of systems, and systems are rarely kind enough to present all of their cross-cutting needs up front.&lt;&#x2F;p&gt;
&lt;p&gt;There is a nice algebraic property hiding in this pattern that deserves more attention. I promise this is not me sneaking category theory into the room in a fake moustache; it is genuinely practical. Middleware and interceptor types almost always admit &lt;code&gt;Semigroup&lt;&#x2F;code&gt; and &lt;code&gt;Monoid&lt;&#x2F;code&gt; instances. WAI&#x27;s &lt;code&gt;Middleware&lt;&#x2F;code&gt; is &lt;code&gt;Application -&amp;gt; Application&lt;&#x2F;code&gt;, an endomorphism, and endomorphisms form a monoid under composition with &lt;code&gt;id&lt;&#x2F;code&gt; as the identity. A record of interceptor hooks, where each field is itself an endomorphism (or a continuation-passing function with a similar shape), gets a fieldwise &lt;code&gt;Semigroup&lt;&#x2F;code&gt; instance for free: &lt;code&gt;a &amp;lt;&amp;gt; b&lt;&#x2F;code&gt; composes each field independently, and &lt;code&gt;mempty&lt;&#x2F;code&gt; is the record where every field is the identity (pass the call through unchanged).&lt;&#x2F;p&gt;
&lt;p&gt;This turns composition from an engineering problem into almost a non-problem. You do not write bespoke plumbing to combine your tracing interceptor with your timeout interceptor with your task queue rewriting interceptor. You &lt;code&gt;mconcat&lt;&#x2F;code&gt; them:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;appTemporalInterceptors =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  mconcat&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    [ retargetingInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , otelInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , sentryInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , sqlApplicationNameInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , loggingContextInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , statementTimeoutInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , teamNameInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , clientExceptionInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    , workflowTypeNameInterceptor&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each interceptor is defined in isolation, in its own module, by someone who only needs to think about one concern. Individual interceptors are built from &lt;code&gt;mempty&lt;&#x2F;code&gt; with one or more fields overridden — everything else passes through. Composition is just &lt;code&gt;(&amp;lt;&amp;gt;)&lt;&#x2F;code&gt;. There is no hidden wiring. There is no coordination tax beyond agreeing on the shape of the hooks. Ordering is explicit in the list. New cross-cutting concerns are added by appending one more element; existing interceptors are never touched.&lt;&#x2F;p&gt;
&lt;p&gt;This is the &lt;code&gt;Monoid&lt;&#x2F;code&gt; pattern at its most practical. The abstraction does not exist for elegance, though elegance is a nice side effect. It exists because it makes the operational task of &quot;combine N independent cross-cutting concerns&quot; trivially correct by construction. If your middleware type supports &lt;code&gt;mempty&lt;&#x2F;code&gt; and &lt;code&gt;(&amp;lt;&amp;gt;)&lt;&#x2F;code&gt;, you can hand it to fifteen engineers and tell them each to write one piece, and the pieces will compose naturally.&lt;&#x2F;p&gt;
&lt;p&gt;Effect systems (&lt;code&gt;effectful&lt;&#x2F;code&gt;, &lt;code&gt;polysemy&lt;&#x2F;code&gt;, &lt;code&gt;fused-effects&lt;&#x2F;code&gt;, &lt;code&gt;cleff&lt;&#x2F;code&gt;, etc.) offer another route. You define an effect type describing the available operations, then provide interpreters that can be swapped at the call site. One for production, one for testing, one that wraps the production interpreter with tracing. The reinterpretation capabilities are particularly nice: intercept an effect, record a metric or inject a delay, and re-send it to the real handler. I wrote a guide to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;fused-effects&#x2F;fused-effects&#x2F;blob&#x2F;main&#x2F;docs&#x2F;reinterpreting_effects.md&quot;&gt;using this for fault injection and observability&lt;&#x2F;a&gt; in the &lt;code&gt;fused-effects&lt;&#x2F;code&gt; documentation, and the core ideas transfer to any effect system. The tradeoff is that effect systems add machinery (type-level effect lists, handler stacks, sometimes gnarly type errors) where records of functions are simple enough that a new hire can understand the pattern in an afternoon. Both work. The important thing is that you pick one and use it consistently enough that your operability story is not itself an archaeological dig.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;persistent&lt;&#x2F;code&gt; library is the canonical positive example, for me. Its &lt;code&gt;SqlBackend&lt;&#x2F;code&gt; type is a record of functions: &lt;code&gt;connPrepare&lt;&#x2F;code&gt;, &lt;code&gt;connInsertSql&lt;&#x2F;code&gt;, &lt;code&gt;connBegin&lt;&#x2F;code&gt;, &lt;code&gt;connCommit&lt;&#x2F;code&gt;, &lt;code&gt;connRollback&lt;&#x2F;code&gt;, and so on. When I implemented OpenTelemetry instrumentation for &lt;code&gt;persistent&lt;&#x2F;code&gt;, I could add tracing spans around every database operation by wrapping the relevant fields. No fork required. Almost no source changes. A few lines of code and we had full visibility into our database layer. That is what a good operational escape hatch looks like.&lt;&#x2F;p&gt;
&lt;p&gt;Libraries that do not do this are the ones that cost us the most operational pain. At Mercury, we very rarely use web API client bindings published on Hackage. This is not because they are necessarily poorly written (some are quite good). The problem is that we cannot trust code we cannot instrument. If a third-party binding makes HTTP calls through concrete functions, we have no way to add tracing, no way to inject timeouts tuned to our SLOs, no way to simulate partner outages in testing, and no way to explain the 400ms gap in a trace except by squinting at it and developing theories. So we write our own. More work upfront, but the clients we write are observable by construction, because we built them that way from the start.&lt;&#x2F;p&gt;
&lt;p&gt;There is also a milder ecosystem tax that people do not always mention. Some of the libraries you depend on are not abandoned, exactly; abandoned is too clean a word. They are still in service, structurally important to the ecosystem, maintained enough that the things continue to run, and not obviously owned by anyone whose full-time job is to improve them.&lt;&#x2F;p&gt;
&lt;p&gt;This has a few causes. Some of the people who built large parts of the modern Haskell ecosystem have, quite reasonably, gone on to other things. A handful of prolific authors of widely used libraries and frameworks seem to have shifted much their attention towards Rust, for example. Their libraries still work, still get support to a degree, and still have users who depend on them. But the stewardship model becomes less clear. Nobody is obviously in charge in the way a fast-moving production team would mean &quot;in charge,&quot; and that creates understandable hesitation around making breaking changes, even when experience has taught us better ways to design these systems.&lt;&#x2F;p&gt;
&lt;p&gt;Diffuse stewardship has technical consequences. Old interfaces tend to persist. Libraries are often stable in the sense that they still function, but not especially alive in the sense of incorporating newer ideas about observability, boundary design, or operability. They may also lag behind advances you would like to take for granted. &lt;code&gt;http-client&lt;&#x2F;code&gt;, for example, still only supports HTTP&#x2F;1.1 directly, which is perfectly serviceable right up until it isn&#x27;t. You can work around this. But it means some parts of the ecosystem feel less like actively evolving products and more like public infrastructure: useful, durable, and mildly resistant to renovation.&lt;&#x2F;p&gt;
&lt;p&gt;This is not a complaint about volunteer maintainers. It is simply one of the ambient risks of building serious systems on a smaller ecosystem. The code may not be abandoned. It may just be standing there, somewhat stoically, waiting for someone with enough context and enough appetite for downstream breakage to renovate the station while the trains are still running.&lt;&#x2F;p&gt;
&lt;p&gt;If you are writing a Haskell library, leave escape hatches. Provide records of functions, or effect types, or callbacks, or &lt;em&gt;something&lt;&#x2F;em&gt; that lets the consumer of your code inject behavior without modifying it. Haskell&#x27;s type system is wonderful for enforcing constraints. But it can also, if you are not careful, seal a system so tightly that the people who have to operate it cannot see inside. The perfect abstraction, if it is operationally opaque, simply cannot be used in production.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-note-to-package-authors&quot;&gt;A note to package authors&lt;&#x2F;h3&gt;
&lt;p&gt;Please consider taking &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hackage.haskell.org&#x2F;package&#x2F;hs-opentelemetry-api&quot;&gt;&lt;code&gt;hs-opentelemetry-api&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; as a dependency and adding instrumentation to your package. The API package is designed to be a good citizen: we are conservative about breaking changes, and it is completely inert when the OpenTelemetry SDK is not initialized by the application. Minimal performance overhead, and no unexpected exceptions or logging in your users&#x27; applications. In the interest of honesty, the dependency footprint is not yet as minimal as we want it to be, and we are actively working on that. But even a handful of spans around your key &lt;code&gt;IO&lt;&#x2F;code&gt; operations would make a real difference to anyone running your library in production.&lt;&#x2F;p&gt;
&lt;p&gt;And please, we beg you, do not log directly from library code. Do not import a logging framework and write to &lt;code&gt;stdout&lt;&#x2F;code&gt; or &lt;code&gt;stderr&lt;&#x2F;code&gt;. Provide a logging callback, or accept a logger as a parameter, or expose your log messages as a data type the caller can route wherever they want. The moment a library decides where logs go, it has made a decision about the operational environment that belongs to the application. At Mercury, we route logs through structured pipelines that feed into our observability stack. When a library writes directly to &lt;code&gt;stderr&lt;&#x2F;code&gt;, those messages bypass everything, and we have to write bespoke plumbing to account for them being dumped into what is otherwise a stream of JSON lines, which is a ridiculous way to spend anyone&#x27;s finite time on Earth. A callback costs you almost nothing. Let the application decide how to be operated.&lt;&#x2F;p&gt;
&lt;p&gt;One more thing for library authors: consider exposing &lt;code&gt;.Internal&lt;&#x2F;code&gt; modules. This is somewhat controversial advice. Others have argued, reasonably, that &lt;code&gt;.Internal&lt;&#x2F;code&gt; modules create implicit API surface that users depend on, making it harder to refactor your library&#x27;s internals without breaking downstream code. The concern is legitimate, but it rests on a degree of confidence that you have gotten your public API exactly right, that you have anticipated every use case your users will encounter. In my experience, that confidence is rarely justified.&lt;&#x2F;p&gt;
&lt;p&gt;A well-documented &lt;code&gt;.Internal&lt;&#x2F;code&gt; module with an explicit stability warning (&quot;this module&#x27;s API may change without notice between minor versions&quot;) is vastly preferable to the alternative, which is that your users fork your package, vendor it into their codebase, and now maintain a divergent copy that will never receive your upstream fixes. The Haskell ecosystem&#x27;s best libraries already do this (&lt;code&gt;containers&lt;&#x2F;code&gt;, &lt;code&gt;text&lt;&#x2F;code&gt;, &lt;code&gt;unordered-containers&lt;&#x2F;code&gt;). It costs almost nothing to provide, and it turns &quot;I need to fork this library&quot; into &quot;I need to import one unstable module,&quot; which is often a better situation for everyone involved.&lt;&#x2F;p&gt;
&lt;p&gt;One thing to watch out for here is that exposing internal modules may mean that you don&#x27;t hear feedback from consumers, who have quietly popped the hood to get at what they need instead. Sometimes that is fine. Sometimes it means your public API&#x27;s missing pieces remain missing because nobody bothers to tell you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-we-don-t-put-in-the-types&quot;&gt;What We Don&#x27;t Put in the Types&lt;&#x2F;h2&gt;
&lt;p&gt;Not all production Haskell is beautiful.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;unsafePerformIO&lt;&#x2F;code&gt; is used in libraries you depend on daily. The &lt;code&gt;bytestring&lt;&#x2F;code&gt; and &lt;code&gt;text&lt;&#x2F;code&gt; libraries use it internally to allocate mutable buffers, write into them, and freeze the result. The types say nothing about what happened during construction. The containment is maintained by convention, by careful reasoning, and by code review. You will encounter this in any production Haskell codebase. You will &lt;em&gt;write&lt;&#x2F;em&gt; this, when the type-safe alternative imposes an unacceptable performance or complexity cost. When you do, be honest about it. Document the invariants that the types are not checking. Be appropriately uncomfortable. Periodically revisit whether the type-safe alternative has become practical. Production Haskell is not the absence of compromise. It is the disciplined containment of compromise.&lt;&#x2F;p&gt;
&lt;p&gt;Then there is the testing situation. An alarming number of Haskell libraries on Hackage have few or no tests. The implicit (and sometimes disastrously explicit) argument is that if it compiles, it works. This is sometimes true for small pure code with tight types. It is certainly a common early Haskell experience: you make illegal states unrepresentable, the compiler catches a shocking amount, and you briefly begin to suspect you may have transcended ordinary software failure.&lt;&#x2F;p&gt;
&lt;p&gt;This feeling should not be trusted.&lt;&#x2F;p&gt;
&lt;p&gt;It is almost never true for IO-heavy code, for code that interacts with external systems, or for code where the interesting bugs live in the &lt;em&gt;semantics&lt;&#x2F;em&gt; rather than the &lt;em&gt;structure&lt;&#x2F;em&gt;. Types can tell you that a function returns an &lt;code&gt;Either ParseError Transaction&lt;&#x2F;code&gt;. They cannot tell you whether it parses the &lt;code&gt;amount&lt;&#x2F;code&gt; field as cents or dollars. They cannot tell you whether your partner API interprets an omitted field differently from a null field. They cannot tell you whether your retry logic doubles charges under exactly one timing window on leap day. In a production context, you are building on top of these libraries, and you are inheriting their untested assumptions. Worth knowing about, and worth compensating for with integration tests at your own layer.&lt;&#x2F;p&gt;
&lt;p&gt;Other ugly bits of Haskell crop up too: orphan instances, partial functions that you swear are total in context, &lt;code&gt;error&lt;&#x2F;code&gt; calls that you promise are unreachable, awkward FFI wrappers, hand-rolled exceptions hierarchy shennanigans, places where the ideal abstraction lost a knife fight with the real one. These accumulate. Operating Haskell in production is the act of tending a living system, and living systems accrete compromises. What you can do is enforce discipline in code review, by way of documentation and examples and tests, so that you know where every compromise is, why it was made, and what would break if it were removed. The goal is not moral purity. The goal is to avoid discovering, during an incident, that half the system&#x27;s assumptions existed only as oral tradition.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-bother-with-haskell-at-all&quot;&gt;Why bother with Haskell at all?&lt;&#x2F;h2&gt;
&lt;p&gt;There is a question that comes up whenever someone considers Haskell for a production system: &lt;strong&gt;is it worth it?&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Not on day one.&lt;&#x2F;p&gt;
&lt;p&gt;On day one, you are slower. The current Haskell ecosystem simply does not have the power of Next.js or Rails to dump a batteries-included hot-reloading development environment onto your desk. Projects like IHP may eventually get there, but inevitably, the library you need does not exist, or exists but is maintained by one person in their spare time. The error messages occasionally read like the screed of a madman who knows exactly what is wrong and deeply resents having to explain it to you.&lt;&#x2F;p&gt;
&lt;p&gt;The hiring concern, though, is overstated. Our CTO Max Tagher has said publicly that backend Haskell engineer is the &lt;em&gt;easiest&lt;&#x2F;em&gt; role to hire for in all of Mercury.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#max&quot;&gt;8&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; There is more demand for Haskell jobs than the market provides, which inverts the usual recruiting dynamic. Interest in Haskell acts as a decent proxy for baseline developer quality (Paul Graham&#x27;s &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.paulgraham.com&#x2F;pypar.html&quot;&gt;Python Paradox&lt;&#x2F;a&gt;, applied one language further up the abstraction ladder), and it attracts people who are genuinely excited about the work rather than indifferent to it. We hire people with deep Haskell experience and people with no Haskell experience at all, and we have a training program that gets the latter group productive in six to eight weeks. The hiring pool problem is real if you need a hundred Haskell experts tomorrow. It is much less real if you are willing to hire good generalists and invest in teaching them.&lt;&#x2F;p&gt;
&lt;p&gt;The hiring risk that nobody warns you about is not the size of the pool. It is the &lt;em&gt;disposition&lt;&#x2F;em&gt; of the pool.&lt;&#x2F;p&gt;
&lt;p&gt;Haskell attracts idealists. This is mostly a strength: you get people who care about correctness, who think carefully about abstractions, who read papers for fun, and who are willing to interrogate assumptions other ecosystems accepted years ago out of pure exhaustion. But idealism, left unchecked, becomes a production liability. The engineer who wants to rewrite the database layer using a novel type-level encoding of relational algebra is not helping you ship features. The engineer who refuses to merge code that uses &lt;code&gt;String&lt;&#x2F;code&gt; instead of &lt;code&gt;Text&lt;&#x2F;code&gt; in a throwaway script is not helping you hit a deadline. The engineer who treats every design discussion as an opportunity to advocate for a total rewrite in the style of a paper they read last week is, however brilliant, making the team slower.&lt;&#x2F;p&gt;
&lt;p&gt;You have to actively cultivate a culture of pragmatism. Haskell gives you powerful tools. Using all of them all the time is not pragmatism; it is self-indulgence. The type system is a power tool, not a religion, and production systems are not the place to treat every feature as an excuse to invent a novel mechanism to solve a problem that already has a perfectly good solution.&lt;&#x2F;p&gt;
&lt;p&gt;The return comes later, but it does come. You see it when a refactor that would take weeks in a dynamically typed codebase takes hours, because the compiler threads the change through every call site and tells you exactly what you missed. There is a particular pleasure to mechanical refactoring in Haskell that is hard to convey until you have experienced it: you change a type, and the compiler hands you an exhaustive list of everywhere that needs to adapt. You work through it methodically — no searching, no guessing, no wondering whether you missed a call site in a module you forgot existed — and when it compiles, you are done. It is one of the few programming experiences that feels genuinely &lt;em&gt;complete&lt;&#x2F;em&gt; rather than &lt;em&gt;probably complete&lt;&#x2F;em&gt;. You see it when a new hire reads a module&#x27;s type signatures and understands its contracts without asking anyone, which in a company that has grown from fifty engineers to several hundred is not a convenience but a survival tactic. You see it when a production incident does &lt;em&gt;not&lt;&#x2F;em&gt; happen because an impossible state was actually impossible, not just considered unlikely by someone in a hurry.&lt;&#x2F;p&gt;
&lt;p&gt;We have found that the investment pays off on the order of months, not years. This is particularly true in financial services, where the cost of a data integrity bug is measured not in user complaints but in regulatory findings and in other people&#x27;s money. The type system does not eliminate these risks. But it gives you the tools to make them harder to introduce by accident, and in a fast-growing codebase, &quot;by accident&quot; is where most of the risk lives.&lt;&#x2F;p&gt;
&lt;p&gt;We have been running Haskell in production for years now, at a scale and in a domain where getting things wrong carries tremendous consequences, both for our customers and for us. It has not been painless. Some of the pain was the language&#x27;s fault, some was the ecosystem&#x27;s, and a good deal of it was our own. But the system works, and it keeps working as the team grows and changes around it, and that is not something I can say about every technology choice I have made in my career.&lt;&#x2F;p&gt;
&lt;p&gt;When I was sixteen, what captivated me about Haskell was the promise that some bugs could be made impossible. I still love that premise, but what I understand now is that the deeper production value is broader and, in a way, more interesting. Haskell lets you preserve hard-won operational knowledge after the people who discovered it have gone home, changed teams, or left the company. It lets you draw hard boundaries around dangerous machinery. It lets you make the safe path the easy path. In a system that has to keep working while the organization around it changes, that surpasses academic elegance and provides solid engineering leverage.&lt;&#x2F;p&gt;
&lt;p&gt;If you are considering Haskell for production, I hope this has given you a realistic picture of what that looks like: not a silver bullet, not a moral crusade, but a genuinely powerful set of tools even when wielded by a team that with a broad range of Haskell expertise.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;Ian Duncan is an engineer on the Stability team at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;mercury.com&quot;&gt;Mercury&lt;&#x2F;a&gt;, where he works on reliability infrastructure, including Mercury&#x27;s Temporal SDK, OpenTelemetry instrumentation, and making sure production doesn&#x27;t keel over and die when he isn&#x27;t looking. He has been writing in assorted functional programming languages professionally since 2014. He lives in The Hague with his family and has strong opinions about coffee, power metal, and system architecture. You can find him on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bsky.app&#x2F;profile&#x2F;iankduncan.com&quot;&gt;Bluesky&lt;&#x2F;a&gt; and at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;iankduncan.com&quot;&gt;iankduncan.com&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;svb&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;In March 2023, Silicon Valley Bank collapsed in what turned out to be the second-largest bank failure in U.S. history. Mercury gained over 8,700 new customers and $2 billion in deposits in five days, because nothing stress-tests your infrastructure quite like the entire startup ecosystem simultaneously deciding to move their money to you. See &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;techcrunch.com&#x2F;2023&#x2F;07&#x2F;07&#x2F;mercury-says-it-gained-nearly-26k-new-customers-in-the-four-months-after-svbs-collapse&#x2F;&quot;&gt;&quot;SVB&#x27;s collapse drove 26K customers to Mercury in 4 months&quot;&lt;&#x2F;a&gt; (TechCrunch, 2023). 95% of them stayed.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;zalora&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;It&#x27;s a thing that I&#x27;ve seen happen to at least three or four companies at this point!&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;safety-one&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;The distinction between studying failure and studying success in complex systems is the central thesis of Erik Hollnagel&#x27;s &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.routledge.com&#x2F;Safety-I-and-Safety-II&#x2F;Hollnagel&#x2F;p&#x2F;book&#x2F;9781472423085&quot;&gt;&lt;em&gt;Safety-I and Safety-II&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; (Ashgate, 2014). The traditional approach (&quot;Safety-I&quot;) focuses on preventing things from going wrong. The alternative (&quot;Safety-II&quot;) focuses on understanding why things ordinarily go &lt;em&gt;right&lt;&#x2F;em&gt; and ensuring the conditions for success are maintained.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;safety-two&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;&quot;Adaptive capacity&quot; as the foundation of resilient systems comes from the resilience engineering literature, particularly David Woods&#x27; work. See &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.routledge.com&#x2F;Resilience-Engineering-Concepts-and-Precepts&#x2F;Woods-Hollnagel-Leveson-Cook&#x2F;p&#x2F;book&#x2F;9780754649045&quot;&gt;&lt;em&gt;Resilience Engineering: Concepts and Precepts&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; (Ashgate, 2006) and Woods&#x27; &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1016&#x2F;j.ress.2018.01.004&quot;&gt;&quot;Four Concepts for Resilience and the Implications for the Future of Resilience Engineering&quot;&lt;&#x2F;a&gt; (&lt;em&gt;Reliability Engineering &amp;amp; System Safety&lt;&#x2F;em&gt;, 2015).&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;patio11&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;Patrick McKenzie (patio11), &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.kalzumeus.com&#x2F;2020&#x2F;10&#x2F;09&#x2F;four-years-at-stripe&#x2F;&quot;&gt;&quot;What Working At Stripe Has Been Like&quot;&lt;&#x2F;a&gt; (2020). At a company growing fast enough, the majority of your colleagues will &lt;em&gt;always&lt;&#x2F;em&gt; be new, which means anything you know that isn&#x27;t written down in code or docs is effectively secret knowledge.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;prr&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;6&lt;&#x2F;sup&gt;
&lt;p&gt;Our process is informed by Google&#x27;s production readiness review model, described in Betsy Beyer et al.&#x27;s &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sre.google&#x2F;sre-book&#x2F;table-of-contents&#x2F;&quot;&gt;&lt;em&gt;Site Reliability Engineering&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; (O&#x27;Reilly, 2016), Chapters 32-33. Our adaptation emphasizes collaborative review over gatekeeping.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;slack&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;7&lt;&#x2F;sup&gt;
&lt;p&gt;Incidentally, Slack at Mercury now has a message lifespan of like a couple years or something before it gets deleted. So, you eventually reach a point at large orgs where deep lore begins to evaporate entirely unless it is written down somewhere more permanent.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;max&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;8&lt;&#x2F;sup&gt;
&lt;p&gt;Max Tagher, &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;serokell.io&#x2F;blog&#x2F;haskell-in-production-mercury&quot;&gt;&quot;Haskell in Production: Mercury&quot;&lt;&#x2F;a&gt; (Serokell, 2022) and &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;serokell.io&#x2F;blog&#x2F;haskell-mercury-functionalfutures&quot;&gt;&quot;Haskell in Mercury&quot;&lt;&#x2F;a&gt; (Functional Futures, 2024).&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;*Mercury is a fintech company, not an FDIC-insured bank. Banking services provided through Choice Financial Group and Column N.A., Members FDIC. (At the time of writing.)&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Haskell Language Server 2.13.0.0 release</title>
        <published>2026-01-26T00:00:00+00:00</published>
        <updated>2026-01-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/hls-2-13-0-0/"/>
        <id>https://blog.haskell.org/hls-2-13-0-0/</id>
        
        <summary type="html">&lt;p&gt;The HLS team is happy to announce the 2.13.0.0 Haskell Language Server release which introduces two new exciting features!&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>A Comment-Preserving Cabal Parser</title>
        <published>2025-12-25T00:00:00+00:00</published>
        <updated>2025-12-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/a-comment-preserving-cabal-parser/"/>
        <id>https://blog.haskell.org/a-comment-preserving-cabal-parser/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/a-comment-preserving-cabal-parser/">&lt;p&gt;&lt;em&gt;This article was originally posted on my blog &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;confusedcompiler.org&#x2F;articles&#x2F;2025-12-comment-preserving-cabal-parser&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;hr&#x2F;&gt;
&lt;h1 id=&quot;motivation&quot;&gt;Motivation&lt;&#x2F;h1&gt;
&lt;p&gt;Cabal &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; is the standard package system for Haskell software, it is similar to Cargo from the Rust
world, or OPAM if you come from OCaml.&lt;&#x2F;p&gt;
&lt;p&gt;Cabal reads cabal package manifests in the cabal format (with the extension &lt;code&gt;.cabal&lt;&#x2F;code&gt;).
However, it is currently unable to modify it loselessly: comments are lost, imports are
fused and written in place, &lt;code&gt;elif&lt;&#x2F;code&gt; in a conditional will be desugared to a nested &lt;code&gt;if&lt;&#x2F;code&gt; in an &lt;code&gt;else&lt;&#x2F;code&gt;.
A tracking issue was opened in cabal&#x27;s repository since 2021 &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;cabal&#x2F;issues&#x2F;7544&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For this reason, critical features such as adding modules to the .cabal manifest or generating
package dependency bounds are implemented poorly.
For example, cabal emits a warning when a module is not listed in the manifest, but it can&#x27;t apply
the fix the warning indicates for you.
Furthermore, &lt;code&gt;cabal gen-bounds&lt;&#x2F;code&gt; (which generates dependency bounds that are not specified) doesn&#x27;t
write dependency bounds information directly to the cabal file, it dumps them to the terminal.
Frustrating!&lt;&#x2F;p&gt;
&lt;p&gt;If we use the current pretty printer in cabal to try to implement these features, the resulting
cabal manifest will be mangled.
This is because cabal can&#x27;t do &lt;em&gt;exact printing&lt;&#x2F;em&gt; yet.
Concretely this means the spaces and empty lines, comments, common stanzas, and if conditions that
you add will be lost.
Adding an exact printer will allow cabal to modify the cabal files automatically and loselessly!&lt;&#x2F;p&gt;
&lt;p&gt;An exact printer is a special kind of pretty printer.
It uses concrete syntax information in the in-memory representation to output a file that is
byte-to-byte identical to the file originally parsed.
To do that, its corresponding parser must store enough information in the in-memory
representation.
Formally, the exact parser&#x2F;printer combination obeys the law presented below.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;forall cabalFile.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #986801;&quot;&gt;  IsValid&lt;&#x2F;span&gt;&lt;span&gt; cabalFile =&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    exactPrint (exactParse cabalFile) == cabalFile&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This reads as &quot;for all valid cabal manifests, the operation &lt;code&gt;exactPrint . exactParse&lt;&#x2F;code&gt; should output a
file that is byte-to-byte identical to the original&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Furthermore, we also aim to allow modification to the in-memory representation. Unmodified parts of
the representation will be printed out verbatim to the original parsed cabal file.&lt;&#x2F;p&gt;
&lt;p&gt;This property is crucial for tools to be able to manipulate cabal files programmatically:
adding dependency, export a module, or generate package bounds just to name a few.&lt;&#x2F;p&gt;
&lt;p&gt;In order to achieve introducing an exact printer, the first step is to preserve concrete syntax information. For example,
comments must not be altered, comma style in comma-separated fields must be preserved, blank spaces
and blank lines should be preserved.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jappie.me&#x2F;&quot;&gt;Jappie&lt;&#x2F;a&gt; started doing a prototype that works well since 2024 Zurihac.
The proposal has matured enough and was accepted by the Haskell Foundation, the current step is
to implement it in Cabal. =D&lt;&#x2F;p&gt;
&lt;p&gt;The following sections will be talking about handling comments in the parser and lexer.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;how-the-cabal-parser-works&quot;&gt;How the cabal parser works&lt;&#x2F;h1&gt;
&lt;p&gt;There are three main parts to the cabal parser, namely the lexer, the field parser, and the field
grammar parser.&lt;&#x2F;p&gt;
&lt;p&gt;The lexer is written in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;haskell-alex.readthedocs.io&#x2F;&quot;&gt;Alex&lt;&#x2F;a&gt;, a lexer generator for Haskell.
Using a lexer generator has the advantage to write a set of production rules without thinking
about their ordering or ambiguity, Alex will detect inconsistencies during the generation of the
lexer. The lexer produces a stream of &lt;code&gt;Token&lt;&#x2F;code&gt;s.&lt;&#x2F;p&gt;
&lt;p&gt;The field parser is written in plain Haskell. It calls the code generated by Alex, consuming the
produced &lt;code&gt;Token&lt;&#x2F;code&gt;s. The top-level (entry point) of field parsers is &lt;code&gt;readFields&lt;&#x2F;code&gt;. It produces a
stream of &lt;code&gt;Field ann&lt;&#x2F;code&gt; where &lt;code&gt;ann&lt;&#x2F;code&gt; holds some annotation related to the field. Before this work on comment
preservation, &lt;code&gt;ann&lt;&#x2F;code&gt; is instantiated as &lt;code&gt;Position&lt;&#x2F;code&gt; which represents the row and column of the source
code where the field is seen.&lt;&#x2F;p&gt;
&lt;p&gt;The field parser not only interacts with the lexer by consuming &lt;code&gt;Token&lt;&#x2F;code&gt;s, but also by changing its
state.
Modeled as an automaton, Alex allows users to set &quot;state&quot;, or &quot;start codes&quot;. Each production rule in
Alex can switch state and either produce a &lt;code&gt;Token&lt;&#x2F;code&gt; or nothing (drop the token for the parser to
never see it). The field parser sets the state of the lexer upon seeing certain &lt;code&gt;Token&lt;&#x2F;code&gt;s that give
hint to the following context.
This has the benefit of producing better lexical error messages because the lexer &quot;is more
knowledgable&quot; thanks to the parser, but also makes the logic a bit intertwined and less
straightforward. In cabal&#x27;s case, certain states are inaccessible without the parser deciding to
transition to it.&lt;&#x2F;p&gt;
&lt;p&gt;The lexer and the field parser together handles the cabal envelope format. Imagine if today cabal
were to not use its own format and uses something like TOML like Cargo from Rust does, this format
is the equivalent of TOML.&lt;&#x2F;p&gt;
&lt;p&gt;The inner format is founded on the &quot;field grammar&quot;, it handles things like &quot;which field should be
defined for a cabal file to be a package description&quot;, &quot;which field can be defined multiple times
with its values merged together&quot;, &quot;which fields contains comma separated values or dependency bound
description&quot;, and so on.&lt;&#x2F;p&gt;
&lt;p&gt;This inner format is handled by the field grammar parser.
It is a lot less straightforward than the two aforementioned parts and is primarily defined with
type classes. The &lt;code&gt;FieldGrammar&lt;&#x2F;code&gt; type class describes how the pretty printer and parser should
behave, two birds one stone! &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;how-we-achieved-comment-preserving-parsing&quot;&gt;How we achieved comment-preserving parsing&lt;&#x2F;h1&gt;
&lt;p&gt;Prior to the work on comment-preserving parsing,
comments are dropped by the lexer and no other parts down the pipeline can see any comments.
To preserve comments, we must go against this prior design by making the lexer emit comments.&lt;&#x2F;p&gt;
&lt;p&gt;We also needed to store the comments somewhere in the output of &lt;code&gt;readFields&lt;&#x2F;code&gt; which outputs
&lt;code&gt;[Field Position]&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;Field ann&lt;&#x2F;code&gt; data type is defined as follows&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;data Field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; Field&lt;&#x2F;span&gt;&lt;span&gt; !(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;) [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;FieldLine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; Section&lt;&#x2F;span&gt;&lt;span&gt; !(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;) [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;SectionArg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;] [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;  deriving&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Eq&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Show&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Functor&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Foldable&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Traversable&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Generic&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Originally, Jappie&#x27;s prototype adds another constructor &lt;code&gt;Comment !ByteString&lt;&#x2F;code&gt; to hold a
comment. This was quite straightforward (exactly what we want in a prototype!) but it also
introduced subtle footguns: all code that depend on the &lt;code&gt;Field&lt;&#x2F;code&gt; data type now has to deal with
with &lt;code&gt;Comment&lt;&#x2F;code&gt;s.&lt;&#x2F;p&gt;
&lt;p&gt;With help from &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;andreabedini&quot;&gt;Andrea Bedini&lt;&#x2F;a&gt;, we changed the design to maintain the
original definition of &lt;code&gt;Field ann&lt;&#x2F;code&gt;, while instantiating &lt;code&gt;ann&lt;&#x2F;code&gt; differently.
Previously this type variable was &lt;code&gt;Position&lt;&#x2F;code&gt;; by changing it to &lt;code&gt;([ByteString], Position)&lt;&#x2F;code&gt;, we were
able to store comments without polluting all usages of &lt;code&gt;Field ann&lt;&#x2F;code&gt; with the &lt;code&gt;Comment&lt;&#x2F;code&gt; constructor!
Concretely we decided to give this pattern a type to disallow uses of &lt;code&gt;Data.Bifunctor.first&lt;&#x2F;code&gt; and
&lt;code&gt;Data.Bifunctor.second&lt;&#x2F;code&gt;, as it can be less straightforward.
The &lt;code&gt;WithComments ann&lt;&#x2F;code&gt; type is as follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;data WithComments&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; WithComments&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; justComments&lt;&#x2F;span&gt;&lt;span&gt; :: ![&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Comment&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; unComments&lt;&#x2F;span&gt;&lt;span&gt; :: !&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;ann&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;  deriving&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Show&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Generic&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Eq&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Ord&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Functor&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A minor downside with the &lt;em&gt;comments-in-&lt;code&gt;ann&lt;&#x2F;code&gt;&lt;&#x2F;em&gt; model is that a file with no field but only comments
can&#x27;t be represented. However we don&#x27;t consider this case because such a file won&#x27;t be a valid cabal
file.&lt;&#x2F;p&gt;
&lt;p&gt;We changed the top-level field parser &lt;code&gt;readFields&lt;&#x2F;code&gt; as follows:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt;-- Old definition is kept with the same type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;readFields&lt;&#x2F;span&gt;&lt;span&gt; :: B8.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;ByteString&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Either ParseError&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Field Position&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;readFields = (fmap . map . fmap) unComments . readFieldsWithComments&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A0A1A7;font-style: italic;&quot;&gt;-- New comment-preserving alternative for the exact printer (and&#x2F;or other future tooling interested in comments)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #4078F2;&quot;&gt;readFieldsWithComments&lt;&#x2F;span&gt;&lt;span&gt; :: B8.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;ByteString&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Either ParseError&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Field&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;WithComments Position&lt;&#x2F;span&gt;&lt;span&gt;)]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;readFieldsWithComments = fmap fst . readFieldsWithComments&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h1 id=&quot;tracing-out-lexer-states&quot;&gt;Tracing out lexer states&lt;&#x2F;h1&gt;
&lt;p&gt;The current cabal already &quot;handles&quot; comments, the problem is that it drops them.
This is good news because we only needed to change the right hand side of the production rule to retain
the comments from the lexer. We didn&#x27;t have to worry that our modifications will alter the syntax of
cabal.&lt;&#x2F;p&gt;
&lt;p&gt;I traced out the automaton graph to see where comments can occur, and added them to the field parser.
The lexer state diagram svg is generated with graphviz, the source code is available &lt;a href=&quot;https:&#x2F;&#x2F;blog.haskell.org&#x2F;a-comment-preserving-cabal-parser&#x2F;.&#x2F;lexer_states.dot&quot;&gt;here&lt;&#x2F;a&gt;.
&lt;img src=&quot;https:&#x2F;&#x2F;blog.haskell.org&#x2F;a-comment-preserving-cabal-parser&#x2F;.&#x2F;lexer_states.svg&quot; alt=&quot;Lexer States&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The start state is used to handle BOM only once and for the purpose of comment parser we can ignore
it.
In the graph, edges with red labels are transitions made by the lexer with its own information; on
the other hand, edges with blue labels are transitions made by the field parser knowing what token
it just saw. The states colored in yellow can emit comments.&lt;&#x2F;p&gt;
&lt;p&gt;Having this diagram is very useful as it tells us how the different contexts relate to each other,
and clarifies which locations can contain comments in a Cabal file.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;FieldLayoutOrBraces&lt;&#x2F;code&gt; and &lt;code&gt;FieldInlineOrBraces&lt;&#x2F;code&gt; would start in &lt;code&gt;in_section&lt;&#x2F;code&gt; state, switch to
&lt;code&gt;in_field_layout&lt;&#x2F;code&gt; or &lt;code&gt;in_field_braces&lt;&#x2F;code&gt; depending on the context and run a parser, and finally switch
back to &lt;code&gt;in_section&lt;&#x2F;code&gt;. To annotate this we use a double ended edge because the transition condition
isn&#x27;t visible from the lexer context.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;functor-multiplicity-is-a-footgun&quot;&gt;Functor multiplicity is a footgun&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;Field ann&lt;&#x2F;code&gt; is a functor, so doing &lt;code&gt;fmap&lt;&#x2F;code&gt; on &lt;code&gt;Field (WithComments Position)&lt;&#x2F;code&gt; we can alter the
annotation on a field, right? Hold on! Let&#x27;s look at the &lt;code&gt;Field ann&lt;&#x2F;code&gt; type again.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;data Field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; Field&lt;&#x2F;span&gt;&lt;span&gt; !(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;) [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;FieldLine&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; Section&lt;&#x2F;span&gt;&lt;span&gt; !(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;) [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;SectionArg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;] [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Field&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; ann&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;  deriving&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Eq&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Show&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Functor&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Foldable&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Traversable&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Generic&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;ann&lt;&#x2F;code&gt; is used everywhere, especially in &lt;code&gt;[FieldLine ann]&lt;&#x2F;code&gt; and &lt;code&gt;[Field ann]&lt;&#x2F;code&gt;.
This means if we &lt;code&gt;fmap&lt;&#x2F;code&gt; and attach comments to a &lt;code&gt;Field&lt;&#x2F;code&gt;, its first and second arguments will all have
the same comments attached!&lt;&#x2F;p&gt;
&lt;p&gt;I think there&#x27;s no way to avoid this, since it&#x27;s still convenient to have a functor instance.
The moral of the story is that you must have a clear understanding of the instances of the data
types you&#x27;re using.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;conclusion-and-future-work&quot;&gt;Conclusion and future work&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;code&gt;GenericPackageDescription&lt;&#x2F;code&gt; is the final representation of a parsed cabal file.
To preserve &lt;code&gt;Eq&lt;&#x2F;code&gt; extensionality &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;, we wrapped it with a data type which isn&#x27;t an instance of &lt;code&gt;Eq&lt;&#x2F;code&gt;.
Otherwise, a &lt;code&gt;GenericPackageDescription&lt;&#x2F;code&gt; would be considered different when the comments are
different!&lt;&#x2F;p&gt;
&lt;p&gt;We duplicated the top-level parsing functions so we have one entry-point that parses
annotation (&lt;code&gt;parseAnnotatedGenericPackagedescription&lt;&#x2F;code&gt;), and another one that doesn&#x27;t
(&lt;code&gt;parseGenericPackageDescription&lt;&#x2F;code&gt;) to provide backward compatibilty.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;data AnnotatedGenericPackageDescription&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #986801;&quot;&gt; AnnotatedGenericPackageDescription&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  {&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; exactComments&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Map Position ByteString&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; unannotatedGpd&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; GenericPackageDescription&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;  deriving&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;Show&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Data&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Generic&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since the position of all comments should not overlap, we store the comments in a map indexed
by their &lt;code&gt;Position&lt;&#x2F;code&gt; in the source file. This is necessary to reconstruct the comments in the source
file and achieve exact print.&lt;&#x2F;p&gt;
&lt;p&gt;This change to the lexer and parser is isolated, meaning you can already use the comments when this is
merged. However more work needs to be done for a complete exact print feature!
Namely we need to stop common stanzas from being merged in.
Currently I am working on a prototype to retain trivia in field grammar parser, so the spaces in
dependency bounds won&#x27;t be dropped, for example.&lt;&#x2F;p&gt;
&lt;p&gt;Please tell me if you have any thoughts on this work. :)&lt;&#x2F;p&gt;
&lt;p&gt;I want to thank the the Haskell Foundation and Jappie for allowing this work to happen.
I appreciate greatly the opportunity to work on cabal and solve a problem that helps Haskell
programmers. :)
I also want to thank the people in my life who believed in me when I needed help, without you I
wouldn&#x27;t be who I am today. :)&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Common Architecture for Building Applications and Libraries, &lt;em&gt;Cabal&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;The printer part doesn&#x27;t even work like we mentioned in the pre-amble,
Jappie told me to say to you all it&#x27;s a lovecraftian horror.
It&#x27;s Gods justice for our collective sin.
If you wonder what an abyss of despair looks like, Jappie invites
you to open &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;hackage-content.haskell.org&#x2F;package&#x2F;Cabal-syntax-3.16.0.0&#x2F;docs&#x2F;Distribution-FieldGrammar-Class.html&quot;&gt;pandora&#x27;s box&lt;&#x2F;a&gt;.
This is your humanity. The face of God. To know it is to know madness.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Extensionality&quot;&gt;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Extensionality&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Analyzing language extension semantics</title>
        <published>2025-11-30T00:00:00+00:00</published>
        <updated>2025-11-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/investigating-language-extension-semantics/"/>
        <id>https://blog.haskell.org/investigating-language-extension-semantics/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/investigating-language-extension-semantics/">&lt;p&gt;Hi I&#x27;m &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jappie.me&quot;&gt;Jappie&lt;&#x2F;a&gt; and I volunteer for the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.haskell.org&#x2F;stability-working-group&#x2F;&quot;&gt;Haskell Foundation Stability Working Group&lt;&#x2F;a&gt;.
Recently we analyzed the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.haskell.org&#x2F;ghc&#x2F;head.hackage&quot;&gt;head.hackage&lt;&#x2F;a&gt; patches to understand
why code breaks on new GHC releases.
&quot;head.hackage&quot; is a repository of patches for Hackage.
GHC engineers use these to test out new GHC builds on a wide range of
Hackage packages without having to upstream&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#upstream&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt; a patch, which can take time.
Instead, they can put the patch in &quot;head.hackage&quot;
and immediately test it on a wide range of packages.
Surprisingly, most breakage wasn’t caused by
&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ghc.gitlab.haskell.org&#x2F;ghc&#x2F;doc&#x2F;users_guide&#x2F;exts&#x2F;template_haskell.html&quot;&gt;Template Haskell&lt;&#x2F;a&gt;,
it came from deeper semantic changes in language extensions.
The meaning of (some) language extensions changed between GHC releases.
This post walks through the main categories of breakage,
why they happened, and what they tell us about long-term stability.
If you care about a smoother upgrade path for Haskell users,
we invite you to participate in the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.haskell.org&#x2F;stability-working-group&#x2F;&quot;&gt;Haskell Foundation Stability Working Group&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Extending our initial &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;jappie.me&#x2F;analyzing-haskell-stability.html&quot;&gt;investigation&lt;&#x2F;a&gt;,
We&#x27;re also interested in understanding &lt;em&gt;why&lt;&#x2F;em&gt; breakage occurs.
So we&#x27;ve recently done further analysis on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ghc.gitlab.haskell.org&#x2F;head.hackage&#x2F;&quot;&gt;head.hackage&lt;&#x2F;a&gt;,
and learned surprisingly enough that the root cause
of a lot of breakage isn&#x27;t Template Haskell,
but seems to be from language extension semantics&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#meaning&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.
We&#x27;re doing this investigation to understand better where efforts
should be focused in improving stability.&lt;&#x2F;p&gt;
&lt;p&gt;This gave us the following table:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;&#x2F;th&gt;&lt;th&gt;Cause&lt;&#x2F;th&gt;&lt;th&gt;Had warnings?&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Cabal-2.4.1.0.patch&lt;&#x2F;td&gt;&lt;td&gt;simplified subsumption&lt;&#x2F;td&gt;&lt;td&gt;no&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Cabal-3.0.2.0.patch&lt;&#x2F;td&gt;&lt;td&gt;simplified subsumption&lt;&#x2F;td&gt;&lt;td&gt;no&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Cabal-3.2.1.0.patch&lt;&#x2F;td&gt;&lt;td&gt;simplified subsumption&lt;&#x2F;td&gt;&lt;td&gt;no&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;data-r-tree-0.6.0.patch&lt;&#x2F;td&gt;&lt;td&gt;parser change (see 1)&lt;&#x2F;td&gt;&lt;td&gt;no&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;drinkery-0.4.patch&lt;&#x2F;td&gt;&lt;td&gt;simplified subsumption&lt;&#x2F;td&gt;&lt;td&gt;no&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ghc-lib-parser-9.8.1.20231121.patch&lt;&#x2F;td&gt;&lt;td&gt;rename forall identifiers (2)&lt;&#x2F;td&gt;&lt;td&gt;yes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;hgeometry-ipe-0.13.patch&lt;&#x2F;td&gt;&lt;td&gt;Instances moved due to splice enforcement&lt;&#x2F;td&gt;&lt;td&gt;no&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;singletons-3.0.2.patch&lt;&#x2F;td&gt;&lt;td&gt;add TypeAbstractions as a language extension&lt;&#x2F;td&gt;&lt;td&gt;yes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;singletons-base-3.1.1.patch&lt;&#x2F;td&gt;&lt;td&gt;add TypeAbstractions as a language extension&lt;&#x2F;td&gt;&lt;td&gt;yes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;vector-space-0.16.patch&lt;&#x2F;td&gt;&lt;td&gt;Star is type (4)&lt;&#x2F;td&gt;&lt;td&gt;yes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;code&gt;th-compat-0.1.4.patch&lt;&#x2F;code&gt; was miscounted so I left that out.
Simplified subsumption appears a lot but 3 are for Cabal,
so it&#x27;s only 2 real occurrences.
I&#x27;d expect that to appear a lot however,
because it was one of &lt;em&gt;the&lt;&#x2F;em&gt; motivating changes for a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.haskell.org&#x2F;stability-working-group&#x2F;&quot;&gt;stability working group&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;simplified-subsumption&quot;&gt;Simplified subsumption&lt;&#x2F;h2&gt;
&lt;p&gt;For the blissfully ignorant reader simplified subsumption causes you
to do this under certain existential conditions:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;--- a&#x2F;Distribution&#x2F;Simple&#x2F;Utils.hs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+++ b&#x2F;Distribution&#x2F;Simple&#x2F;Utils.hs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -1338,7 +1338,7 @@ withTempFileEx opts tmpDir template action =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     (\(name, handle) -&amp;gt; do hClose handle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                            unless (optKeepTempFiles opts) $&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                              handleDoesNotExist () . removeFile $ name)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-    (withLexicalCallStack (uncurry action))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+    (withLexicalCallStack (\x -&amp;gt; uncurry action x))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You have to insert a lambda, which apparently has some performance impact.
This had a big impact on &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.yesodweb.com&#x2F;book&quot;&gt;Yesod stacks&lt;&#x2F;a&gt;,
whose code generation helpfully created
the database alias in the template:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;haskell&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;type DB&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; forall&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;m&lt;&#x2F;span&gt;&lt;span&gt; ::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Type&lt;&#x2F;span&gt;&lt;span&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; Type&lt;&#x2F;span&gt;&lt;span&gt;).&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt;MonadUnliftIO&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; m&lt;&#x2F;span&gt;&lt;span&gt;) =&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A626A4;&quot;&gt; ReaderT SqlBackend&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E45649;&quot;&gt; m a&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Normally this is quite convenient,
however with the simplified subsumption change,
any code that interacts with the database now has to insert those lambdas.
As you can imagine this would in many places for a commercial code base.
Causing a lot of compile errors for industrial users.
Instead of inserting lambdas, you can also delete those existential aliases to solve the problem.
Or you can enable the language extension: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;~ghc&#x2F;9.12.2&#x2F;docs&#x2F;users_guide&#x2F;exts&#x2F;rank_polymorphism.html#extension-DeepSubsumption&quot;&gt;DeepSubsumption&lt;&#x2F;a&gt;.
Which restores the original behavior.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;moving-of-instances-due-to-template-haskell&quot;&gt;Moving of instances due to Template Haskell&lt;&#x2F;h2&gt;
&lt;p&gt;This change forces you to put the instances above the splice where
they are being used in the same module.
A dear colleague decided to generate instances in Template Haskell.
That was quite the puzzle!
I asked the GHC devs why they did this,
and it turns out this was a soundness issue in the typechecker.
Here, soundness means the type system can&#x27;t be tricked into allowing invalid programs.
So the community is better off, despite this causing a fair bit of work.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;--- a&#x2F;src&#x2F;Ipe&#x2F;Content.hs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;+++ b&#x2F;src&#x2F;Ipe&#x2F;Content.hs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -288,6 +288,14 @@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+instance Fractional r =&amp;gt; IsTransformable (IpeObject r) where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+  transformBy t (IpeGroup i)     = IpeGroup     $ i&amp;amp;core %~ transformBy t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+ ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; makePrisms &amp;#39;&amp;#39;IpeObject&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;@@ -303,14 +311,6 @@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-instance Fractional r =&amp;gt; IsTransformable (IpeObject r) where&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-  transformBy t (IpeGroup i)     = IpeGroup     $ i&amp;amp;core %~ transformBy t&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;- ...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;1-parser-change&quot;&gt;(1) Parser change&lt;&#x2F;h2&gt;
&lt;p&gt;The parser is the component of the compiler that transforms text
into a memory structure the compiler can work with.
This structure is called an abstract syntax tree.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-      Node4 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a), getC3 :: ! (RTree a), getC4 :: ! (RTree a) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-    | Node3 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a), getC3 :: ! (RTree a) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-    | Node2 {getMBB :: {-# UNPACK #-} ! MBB, getC1 :: ! (RTree a), getC2 :: ! (RTree a) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+      Node4 {getMBB :: {-# UNPACK #-} !MBB, getC1 :: !(RTree a), getC2 :: !(RTree a), getC3 :: !(RTree a), getC4 :: !(RTree a) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+    | Node3 {getMBB :: {-# UNPACK #-} !MBB, getC1 :: !(RTree a), getC2 :: !(RTree a), getC3 :: !(RTree a) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+    | Node2 {getMBB :: {-# UNPACK #-} !MBB, getC1 :: !(RTree a), getC2 :: !(RTree a) }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     | Node  {getMBB ::                  MBB, getChildren&amp;#39; :: [RTree a] }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-    | Leaf  {getMBB :: {-# UNPACK #-} ! MBB, getElem :: a}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+    | Leaf  {getMBB :: {-# UNPACK #-} !MBB, getElem :: a}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     | Empty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a change from all the way back in 2020, where the &lt;em&gt;core&lt;&#x2F;em&gt; language changed by
disallowing &lt;code&gt;!&lt;&#x2F;code&gt; before parens.
Here the bang &lt;code&gt;!&lt;&#x2F;code&gt; indicates strict fields.
Technically this doesn&#x27;t fit into the category
because the core language isn&#x27;t a language extension.
But semantics did change!
Actually I don&#x27;t think we expected to find something like this at all.
I&#x27;m not sure how relevant this is to discuss further because it appears
quite rare for someone to do this.
You can enable &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;ghc&#x2F;latest&#x2F;docs&#x2F;users_guide&#x2F;exts&#x2F;strict.html#extension-StrictData&quot;&gt;StrictData&lt;&#x2F;a&gt;
in your Cabal file and delete all those bangs!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;2-rename-forall-identifiers&quot;&gt;(2) Rename forall identifiers&lt;&#x2F;h2&gt;
&lt;p&gt;This changes the forall identifier into a keyword at term level.
It already was at the type level.
The issue is discussed &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.haskell.org&#x2F;ghc&#x2F;ghc&#x2F;-&#x2F;issues&#x2F;23719&quot;&gt;here&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; hintExplicitForall :: Located Token -&amp;gt; P ()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; hintExplicitForall tok = do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-    forall   &amp;lt;- getBit ExplicitForallBit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+    forAll   &amp;lt;- getBit ExplicitForallBit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     rulePrag &amp;lt;- getBit InRulePragBit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-    unless (forall || rulePrag) $ addError $ mkPlainErrorMsgEnvelope (getLoc tok) $&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+    unless (forAll || rulePrag) $ addError $ mkPlainErrorMsgEnvelope (getLoc tok) $&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       (PsErrExplicitForall (isUnicode tok))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;3-typeabstractions&quot;&gt;(3) TypeAbstractions&lt;&#x2F;h2&gt;
&lt;p&gt;From what I understand from the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ghc.gitlab.haskell.org&#x2F;ghc&#x2F;doc&#x2F;users_guide&#x2F;exts&#x2F;type_abstractions.html#type-abstractions&quot;&gt;manual&lt;&#x2F;a&gt;
is that part of the syntax for type abstractions landed in GHC 9.2,
however 9.8 and onwards requires you to enable this language extension.
This appeared because certain new functionality was introduced behind an
old language extension flag, according to &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ghc-proposals&#x2F;ghc-proposals&#x2F;blob&#x2F;master&#x2F;proposals&#x2F;0448-type-variable-scoping.rst#4type-arguments-in-constructor-patterns&quot;&gt;this proposal&lt;&#x2F;a&gt;. It says we don&#x27;t want to introduce new functionality behind established extensions,
so that&#x27;s why we require TypeAbstractions now,
where previously ScopedTypeVariables and TypeApplications were enough.&lt;&#x2F;p&gt;
&lt;p&gt;This extension enables you to bind type variables in pattern matches.
I don&#x27;t know why this happened like this, but it happened in 2023:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+-- Invisible type binders in type declarations, such as&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+--&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+--   type family Sing @k&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+--&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+-- require the TypeAbstractions extension.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+#if __GLASGOW_HASKELL__ &amp;gt;= 909&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+{-# LANGUAGE TypeAbstractions #-}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+#endif&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;4-star-is-type&quot;&gt;(4) Star is type&lt;&#x2F;h2&gt;
&lt;p&gt;This change was announced via a warning.
It tells users to write &lt;code&gt;Type&lt;&#x2F;code&gt; instead of &lt;code&gt;*&lt;&#x2F;code&gt; for kinds representing types.
A kind is essentially the type of a type,
and as a concept is used for type-level programming type safety.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E45649;&quot;&gt;-  type Basis v :: *&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #50A14F;&quot;&gt;+  type Basis v :: Type&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;Often these breakages are annoying and frustrating.
But if we look deeper, we find that each of them has
a little story
and good reasons for being introduced.
If you find this all as interesting as I do,
please consider joining some of the stability
working group meetings!&lt;&#x2F;p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;upstream&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;Upstreaming is the process of sending a patch to the “maintainers” of an open-source project. The maintainers will then make the patch ‘official’ by merging it. In principle, the process is simple, but in practice, the burden of proof (especially for larger projects) is on the person who submitted the patch. They have to convince the maintainers that the patch is useful, which takes time in the form of communication&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;meaning&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;The precise meaning of features enabled by language extensions. I guess parser changes also count.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Google Summer of Code 2025 Wrap-up</title>
        <published>2025-11-24T00:00:00+00:00</published>
        <updated>2025-11-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/gsoc-2025/"/>
        <id>https://blog.haskell.org/gsoc-2025/</id>
        
        <summary type="html">&lt;p&gt;The Haskell.org committee is pleased to present the results of Haskell&#x27;s
participation in the Google Summer of Code 2025. This marks our 14th time
taking part in GSoC!&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>GHC 9.14.1-alpha1 is now available</title>
        <published>2025-08-19T00:00:00+00:00</published>
        <updated>2025-08-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/ghc-9-14-1-alpha1/"/>
        <id>https://blog.haskell.org/ghc-9-14-1-alpha1/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/ghc-9-14-1-alpha1/">&lt;p&gt;The GHC developers are very pleased to announce the availability of the
first alpha prerelease of GHC 9.14.1. Binary distributions, source
distributions, and documentation are available at &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;ghc&#x2F;9.14.1-alpha1&quot;&gt;downloads.haskell.org&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;GHC 9.14 will bring a number of new features and improvements, including:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Significant improvements in specialisation:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;SPECIALISE&lt;&#x2F;code&gt; pragma now allows use of type application syntax&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;SPECIALISE&lt;&#x2F;code&gt; pragma can be used to specialise for expression arguments
as well as type arguments.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Specialisation is now considerably more reliable in the presence of
&lt;code&gt;newtype&lt;&#x2F;code&gt;s&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;the specialiser is now able to produce specialisations with
polymorphic typeclass constraints, considerably broadening its scope.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Significant improvements in the GHCi debugger&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Record fields can be defined to be non-linear when &lt;code&gt;LinearTypes&lt;&#x2F;code&gt; is enabled.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RequiredTypeArgments&lt;&#x2F;code&gt; can now be used in more contexts&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;SSE&#x2F;AVX support in the x86 native code generator backend&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;A major update of the Windows toolchain&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;... and many more&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A full accounting of changes can be found in the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;ghc&#x2F;9.14.1-alpha1&#x2F;docs&#x2F;users_guide&#x2F;9.14.1-notes.html&quot;&gt;release notes&lt;&#x2F;a&gt;. Given the
many specialisation improvements and their potential for regression, we would
very much appreciate testing and performance characterisation on downstream
workloads.&lt;&#x2F;p&gt;
&lt;p&gt;Due to unexpected complications, this initial prerelease comes a bit later than
expected. Consequently, we expect to have three condensed alphas prior to the
release candidate. We expect the next alpha
will come the week of 9 Sept. 2025, while the third will come 23 Sept. 2025,
with the release candidate coming 7 Oct. 2025.&lt;&#x2F;p&gt;
&lt;p&gt;We would like to thank the Zw3rk stake pool,
Well-Typed, Mercury, Channable, Tweag I&#x2F;O, Serokell, SimSpace, the Haskell
Foundation, and other anonymous contributors whose on-going financial
and in-kind support has facilitated GHC maintenance and release
management over the years. Finally, this release would not have been
possible without the hundreds of open-source contributors whose work
comprise this release.&lt;&#x2F;p&gt;
&lt;p&gt;As always, do give this release a try and open a &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gitlab.haskell.org&#x2F;ghc&#x2F;homepage&#x2F;-&#x2F;issues&#x2F;new&quot;&gt;ticket&lt;&#x2F;a&gt; if you see
anything amiss.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Cabal 3.16 release</title>
        <published>2025-07-24T00:00:00+00:00</published>
        <updated>2025-07-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/cabal-3-16-0-0-release/"/>
        <id>https://blog.haskell.org/cabal-3-16-0-0-release/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/cabal-3-16-0-0-release/">&lt;p&gt;The Cabal release team brings you a new release of the cabal-install tool and accompanying libraries, version 3.16.0.0. This release supports the (not yet available) GHC 9.14, including its upcoming alpha. We expect to publish cabal-install 3.16.1.0 soon after GHC 9.14 is out and address any discovered issues and incompatibilities that can be fixed in the respective timeframe.&lt;&#x2F;p&gt;
&lt;p&gt;The binaries for cabal-install are available:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;In the GHCup main channel (we thank the GHCup maintainers for the swift support of our release).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;downloads.haskell.org&#x2F;cabal&#x2F;cabal-install-3.16.0.0&#x2F;&quot;&gt;On our website&lt;&#x2F;a&gt; (the same set of binaries can also be installed via the GHCup vanilla channel); these binaries are signed by &quot;Francesco Ariis &lt;a href=&quot;mailto:francesco@ariis.it&quot;&gt;francesco@ariis.it&lt;&#x2F;a&gt;&quot; (fingerprint: &lt;code&gt;DAFB 4D8A F684 1435 18D5  051F A9AF 0AAA 6B87 EC51&lt;&#x2F;code&gt;; the key is hosted on keyserver.ubuntu.com).&lt;&#x2F;li&gt;
&lt;li&gt;As usual, &lt;code&gt;cabal update &amp;amp;&amp;amp; cabal install cabal-install-3.16.0.0&lt;&#x2F;code&gt; is an option too.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;what-s-new&quot;&gt;What&#x27;s new&lt;&#x2F;h3&gt;
&lt;p&gt;Some of the cool features in the release:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The new &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;cabal.readthedocs.io&#x2F;en&#x2F;stable&#x2F;cabal-commands.html#cabal-target&quot;&gt;&lt;code&gt;cabal target&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; command;&lt;&#x2F;li&gt;
&lt;li&gt;Faster Git clones;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cabal haddock-project&lt;&#x2F;code&gt; handles user-provided CSS;&lt;&#x2F;li&gt;
&lt;li&gt;Multiple &lt;code&gt;flags&lt;&#x2F;code&gt; stanzas in &lt;code&gt;cabal.project&lt;&#x2F;code&gt; accumulate values instead of using the last one;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;cabal gen-bounds&lt;&#x2F;code&gt; works with multi-package projects (i.e., embraces the &lt;code&gt;v2&lt;&#x2F;code&gt;-commands infrastructure);&lt;&#x2F;li&gt;
&lt;li&gt;Cabal multi-repl supports reexported-modules with renaming for GHC &amp;gt;= 9.12, and more!&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;See the &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;cabal&#x2F;blob&#x2F;master&#x2F;release-notes&#x2F;cabal-install-3.16.0.0.md&quot;&gt;cabal-install release notes on Github&lt;&#x2F;a&gt; for the full changelog. Power users may be interested in &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;cabal&#x2F;blob&#x2F;master&#x2F;release-notes&#x2F;Cabal-3.16.0.0.md&quot;&gt;Cabal-the-library notes&lt;&#x2F;a&gt; too.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-16-0-0-contributors&quot;&gt;3.16.0.0 Contributors&lt;&#x2F;h3&gt;
&lt;p&gt;The credits go to: Andreas Abel, Andreas Klebinger, Artem Pelenitsyn, Benjamin, Benjamin McRae, Berk Özkütük, Bodigrim, brandon s allbery kf8nh, Bryan Richter, Francesco Ariis, Francesco Gazzetta, Gleb Popov, Hécate Moonlight, James Blackburn, Jaro, Jasper Van der Jeugt, Javier Sagredo, Jens Petersen, Kazu Yamamoto, Kevin Quick, Leonid Znamenok, malteneuss, Matthew Pickering, Matt Parsons, Mike Pilgrem, Mikolaj Konarski, Moritz Angermann, noiioiu, parsonsmatt, Peter Becich, Phil de Joux, PHO, Praneya Kumar, Rebecca Turner, Rodrigo Mesquita, Sdywolf, Serge S. Gulin, Sergey Vinokurov, sheaf, Sylvain Henry, Teo Camarasu, theGhostJW, Tom Smeding, Trevis, Troels Henriksen, Yi Fang, Yuto Takano, zlonast, Zubin Duggal.&lt;&#x2F;p&gt;
&lt;p&gt;We thank all the contributors as well as our reviewers, QA testers, devops, and others without whom this release wouldn’t be possible.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;feedback&quot;&gt;Feedback&lt;&#x2F;h3&gt;
&lt;p&gt;Please report any issues you notice with the 3.16.0.0 release on our GitHub: &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;cabal&#x2F;issues&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;haskell&#x2F;cabal&#x2F;issues&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;— Cabal release team (Artem, Brandon, Francesco, Mikołaj)&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>GHC LTS Releases</title>
        <published>2025-07-07T00:00:00+00:00</published>
        <updated>2025-07-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/ghc-lts-releases/"/>
        <id>https://blog.haskell.org/ghc-lts-releases/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/ghc-lts-releases/">&lt;h1 id=&quot;ghc-will-start-maintaining-an-lts-release-branch-in-the-near-future&quot;&gt;GHC will start maintaining an LTS release&#x2F;branch in the near future&lt;&#x2F;h1&gt;
&lt;p&gt;A release being designated LTS (Long Term Support) in this case means we plan
to support it over a longer timeframe than usual.&lt;&#x2F;p&gt;
&lt;p&gt;Concretely the plan is to provide updates for a LTS releases for &lt;em&gt;at least&lt;&#x2F;em&gt; two
years. Most likely we will support LTS releases for even longer than that,
aiming for a support window of three years currently.&lt;&#x2F;p&gt;
&lt;p&gt;During this time we will be providing minor releases fixing bugs as with any
other release. The main difference being that we will do so for a longer period
of time.&lt;&#x2F;p&gt;
&lt;p&gt;There are no plans to backport any new features to LTS releases after their
initial release.&lt;&#x2F;p&gt;
&lt;p&gt;In terms of frequency of LTS releases we plan to have an overlap between LTS
support windows of different LTS series of six months.&lt;&#x2F;p&gt;
&lt;p&gt;A potential timeline might then look like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #383A42; background-color: #FAFAFA;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025 Aug - LTS 9.14 released&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2028 Spring - LTS 9.22 released&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2028 Summer - LTS 9.14.X - last 9.14 point release&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2031 Spring - LTS 9.X released&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2031 Summer - Last 9.22 point release&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;…&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h1 id=&quot;non-lts-releases&quot;&gt;Non-LTS releases&lt;&#x2F;h1&gt;
&lt;p&gt;GHC will continue to release new major non-lts releases on a ~6 Month cadence.
We expect to cut back on the lifetime of these releases slightly, dedicating
the resources freed up this way to enable a longer support window for the LTS
releases.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-lts-releases&quot;&gt;Why LTS releases?&lt;&#x2F;h1&gt;
&lt;p&gt;In practice some releases always saw more adoption than others by users. The
GHC Team has not been blind to this fact and has at times informally extended
the life of a certain release based on this as well.&lt;&#x2F;p&gt;
&lt;p&gt;This resulted in a sort of informal &quot;post-hoc LTS&quot; status of releases. At times
with support windows not much shorter than our proposed minimum of two years.&lt;&#x2F;p&gt;
&lt;p&gt;This worked reasonable well for people who were confident to stay on a fairly
old release, only upgrading to a newer &quot;post-hoc LTS&quot; once the dust settled. It
also worked out for those who picked one of those &quot;post-hoc LTS&quot; releases by
happenstance before it was clear the release would end up as &quot;post-hoc LTS&quot;.&lt;&#x2F;p&gt;
&lt;p&gt;However users who adopted major releases which did not end up as &quot;post-hoc LTS&quot;
often had to choose between upgrading earlier than expected, or risk running
into a show stopping bug after the support window of the release had already
ended. Similarly much of this was based on informal community sentiment and
rarely written down explicitly. Making this information hard to access for
members not deeply involved in the day to day of the haskell community.&lt;&#x2F;p&gt;
&lt;p&gt;By designating a major release as LTS ahead of time we hope that users can make
a informed decision about which GHC version they pick. Making it clear what the
tradeoffs will be. With a clear choice between a longer support window or the
newest features.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;why-not-make-post-hoc-lts-releases-official-instead&quot;&gt;Why not make post-hoc LTS releases official instead?&lt;&#x2F;h1&gt;
&lt;p&gt;This is a question that has come up a lot in discussion. The major downsides of
this are a lack of predictability, and that a lot of time might be lost between
the initial release and any such decision. If we declare a release as LTS 9
months after its .1 release we essentially shaved off months from the LTS
support window.&lt;&#x2F;p&gt;
&lt;p&gt;On the flip side if we announce it ahead of time everyone knows that a given
release will be the new LTS. So the hope is that this encourages more and
quicker support for the release by the community. Hopefully compressing the
timeline of bug fixing, testing and eventual widespread adoption.&lt;&#x2F;p&gt;
&lt;p&gt;Overall I&#x27;m hopeful that LTS releases being explicit will remove a lot of
ambiguity around GHC versions. And while the guaranteed LTS support window
might not be as long as one might hope having LTS releases with longer
guaranteed support window should still be helpful to people working on long
running haskell projects.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;next-steps&quot;&gt;Next steps&lt;&#x2F;h1&gt;
&lt;p&gt;The first LTS release will be GHC 9.14, which will be released this summer!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Haskell Language Server 2.10.0.0 release</title>
        <published>2025-04-15T00:00:00+00:00</published>
        <updated>2025-04-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/hls-2-10-0-0/"/>
        <id>https://blog.haskell.org/hls-2-10-0-0/</id>
        
        <summary type="html">&lt;p&gt;The HLS Team is proud to announce the new release of the Haskell Language Server &lt;code&gt;2.10.0.0&lt;&#x2F;code&gt;!&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Introducing the Haskell Foundation Stability Working Group</title>
        <published>2025-03-31T00:00:00+00:00</published>
        <updated>2025-03-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/stability-working-group/"/>
        <id>https://blog.haskell.org/stability-working-group/</id>
        
        <summary type="html">&lt;p&gt;What is stability? What has the Haskell Foundation Stability Working Group been working on?&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Case Study — Using a JavaScript component inside a Haskell application</title>
        <published>2025-01-08T00:00:00+00:00</published>
        <updated>2025-01-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/case-study-foreign-integration-js-browser/"/>
        <id>https://blog.haskell.org/case-study-foreign-integration-js-browser/</id>
        
        <summary type="html">&lt;p&gt;GHC since version 9.8 allows us to create callbacks from JS to Haskell code, which enables us to create full-fledged browser apps.
This article shows how to use the JS backend with foreign component libraries.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>GHC 9.12 &amp; Cabal 3.14 releases</title>
        <published>2025-01-06T00:00:00+00:00</published>
        <updated>2025-01-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/ghc-9-12-cabal-3-14/"/>
        <id>https://blog.haskell.org/ghc-9-12-cabal-3-14/</id>
        
        <summary type="html">&lt;p&gt;To conclude the year 2024, the GHC and Cabal teams are happy to announce the releases of GHC 9.12 and cabal 3.14.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>How to collect performance statistics with Cabal</title>
        <published>2024-12-23T00:00:00+00:00</published>
        <updated>2024-12-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/how-to-collect-performance-statistics-with-cabal/"/>
        <id>https://blog.haskell.org/how-to-collect-performance-statistics-with-cabal/</id>
        
        <summary type="html">&lt;p&gt;The &lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;cabal.readthedocs.io&#x2F;en&#x2F;latest&#x2F;&quot;&gt;Cabal Manual&lt;&#x2F;a&gt; now has a guide on how to collect performance statistics of Haskell applications.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Google Summer of Code 2024 Wrap-up</title>
        <published>2024-11-21T00:00:00+00:00</published>
        <updated>2024-11-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/gsoc-2024/"/>
        <id>https://blog.haskell.org/gsoc-2024/</id>
        
        <summary type="html">&lt;p&gt;The Haskell.org committee is pleased to present the results of Haskell&#x27;s
participation in the Google Summer of Code 2024. This marks our 13th time
taking part in GSoC!&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Haskell.org and the Haskell Foundation Join Forces</title>
        <published>2024-09-25T00:00:00+00:00</published>
        <updated>2024-09-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/haskell-foundation-and-committee-merger/"/>
        <id>https://blog.haskell.org/haskell-foundation-and-committee-merger/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/haskell-foundation-and-committee-merger/">&lt;p&gt;The members of the Haskell.org Committee (the Committee) and the directors of the Haskell Foundation (the Foundation) are pleased to announce that we have joined forces, with the aim (subject to regulatory approval) of merging Haskell.org, Inc. and Haskell Foundation, Inc. into a single not-for-profit corporation. Together we continue our common mission to advance functional programming and to advocate for the Haskell language, its ecosystem, and its community.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-committee&quot;&gt;The Committee&lt;&#x2F;h2&gt;
&lt;p&gt;The Committee has a long history of supporting the Haskell community and ecosystem, supervising the resources under the &quot;haskell.org&quot; domain, including the www.haskell.org website, Hackage, Discourse, and the Haskell mailing lists, as well as organizing events such as Haskell.org’s participation in Google Summer of Code and the Haskell Summer of Code.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-foundation&quot;&gt;The Foundation&lt;&#x2F;h2&gt;
&lt;p&gt;The Foundation is a newer organization, dedicated to broadening the adoption of Haskell by supporting its ecosystem of tools, libraries, education, and research. It has had similar goals to Haskell.org, and been engaged in complementary activities. Amongst its activities, it facilitates cross-team collaboration on fundamental tooling (e.g. Cabal, Stack, GHCup and the Haskell Language Server), hosts Stackage, financially supports the development of essential ecosystem components (e.g. the GHCup project and Botan bindings), runs workshops that help new contributors to fundamental tooling, and actively seeks financial sponsorship to help fund these efforts.&lt;&#x2F;p&gt;
&lt;p&gt;Both corporations are Not-for-Profit corporations and Haskell.org Inc. has also been recognised as exempt from taxation in the USA. For that reason, most of the transactions of the Foundation are conducted by Haskell.org, Inc. One of the biggest factors in the desire to merge corporate entities is to make it easier to manage the legal and financial responsibilities around non-profit status and holding funds. Having only one corporation would also address a source of confusion for donors and sponsors.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-next&quot;&gt;What Next?&lt;&#x2F;h2&gt;
&lt;p&gt;The Committee will continue to serve the community as an important committee of the Foundation. The Committee will retain all its current responsibilities, including independent editorial control of the Haskell.org website. It will also continue to oversee much of the Haskell community infrastructure and manage Haskell&#x27;s participation in Google Summer of Code.&lt;&#x2F;p&gt;
&lt;p&gt;Joining forces allows us to better share resources and work together while at the same time allowing the Committee (as part of the Foundation) to retain its independence over its domain.&lt;&#x2F;p&gt;
&lt;p&gt;The Committee members and the directors of the Foundation remain deeply grateful for the opportunity to serve the Haskell community. We look forward to continuing this support with greater effectiveness and efficiency as a single unified organization.&lt;&#x2F;p&gt;
&lt;p&gt;Sincerely,&lt;br &#x2F;&gt;
The Haskell.org Committee and The Haskell Foundation&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Documentation Best Practices in 2024</title>
        <published>2024-09-15T00:00:00+00:00</published>
        <updated>2024-09-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/documentation-best-practices-in-2024/"/>
        <id>https://blog.haskell.org/documentation-best-practices-in-2024/</id>
        
        <summary type="html">&lt;p&gt;In the Haddock team, part of our mission is to help with writing documentation, and promoting best practices. This article will help you write the best documentation you can!&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>JavaScript Code Minification Report</title>
        <published>2024-09-15T00:00:00+00:00</published>
        <updated>2024-09-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/report-of-js-code-minification/"/>
        <id>https://blog.haskell.org/report-of-js-code-minification/</id>
        
        <summary type="html">&lt;p&gt;The JavaScript world has been battling for low bundle size from the very beginning.
It is now our turn to enter the battle&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>The Haskell Playground</title>
        <published>2024-09-15T00:00:00+00:00</published>
        <updated>2024-09-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/the-haskell-playground/"/>
        <id>https://blog.haskell.org/the-haskell-playground/</id>
        
        <summary type="html">&lt;p&gt;The playground (&lt;a rel=&quot;noopener external&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;play.haskell.org&quot;&gt;play.haskell.org&lt;&#x2F;a&gt;) allows you to run single-file Haskell programs right from your browser, and share them with others.
In this post, I will introduce the playground and give some implementation details.&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Welcome</title>
        <published>2024-09-14T00:00:00+00:00</published>
        <updated>2024-09-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.haskell.org/intro/"/>
        <id>https://blog.haskell.org/intro/</id>
        
        <content type="html" xml:base="https://blog.haskell.org/intro/">&lt;p&gt;The Haskell Ecosystem plays host to some amazing projects. Talented developers spend significant amounts of time, often their free time, helping develop, maintain, and support this ecosystem that all Haskell developers use. This space is for all of the developer teams that work on Haskell core infrastructure and power the Haskell Ecosystem.&lt;&#x2F;p&gt;
&lt;p&gt;This blog will provide a space for these efforts to communicate with the wider Haskell community. Things you might see here: calls for volunteers, project updates, requests for comments&#x2F;feedback.&lt;&#x2F;p&gt;
&lt;p&gt;Blogs also form a key part of an ecosystem’s documentation. We’d like to take this idea to its conclusion in the Haskell ecosystem and provide a place for this high-value documentation to live and be maintained. In addition to helping out with editing, by hosting the posts here we’ll also have shared responsibility for maintaining the blog posts, helping prevent the all-too-common bit-rot that occurs on blog posts.&lt;&#x2F;p&gt;
&lt;p&gt;There are already a few posts to get the ball rolling. We’re really excited to share these thoughts with you all.&lt;&#x2F;p&gt;
&lt;p&gt;Happy Hacking,&lt;&#x2F;p&gt;
&lt;p&gt;José Manuel Calderón Trilla&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
