<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Vehbi Sinan Tunalioglu</title>
    <description>The latest articles on Forem by Vehbi Sinan Tunalioglu (@vst).</description>
    <link>https://forem.com/vst</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1252326%2Ff735c1ce-e203-4b74-8c40-76f65dad71d7.jpeg</url>
      <title>Forem: Vehbi Sinan Tunalioglu</title>
      <link>https://forem.com/vst</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vst"/>
    <language>en</language>
    <item>
      <title>I Blogged Every Day for a Month -- Again</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Sat, 31 May 2025 14:54:49 +0000</pubDate>
      <link>https://forem.com/vst/i-blogged-every-day-for-a-month-again-pkg</link>
      <guid>https://forem.com/vst/i-blogged-every-day-for-a-month-again-pkg</guid>
      <description>&lt;p&gt;Today marks the final day of my second month-long blogging challenge. The first was in &lt;a href="https://thenegation.com/posts/blogging-challenge/" rel="noopener noreferrer"&gt;August&lt;/a&gt; last year. This one, in May, felt different -- but just as transformative.&lt;/p&gt;

&lt;p&gt;Here are my takeaways.&lt;/p&gt;

&lt;h2&gt;
  
  
  The grind is real -- and it teaches you
&lt;/h2&gt;

&lt;p&gt;I have a few jobs to do, with different hats to wear. I also have a family. So, blogging almost always happened late in the evenings.&lt;/p&gt;

&lt;p&gt;Some evenings I had not even thought about what to write until 9pm-10pm. Then I had to find a topic, write, and publish. It was stressful and felt like a grind. But this taught me something important: &lt;strong&gt;grind is everywhere&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you are a marketer, you grind to get leads. If you are a developer, you grind to fix bugs and run chores for housekeeping. The examples may change, but the grind stays.&lt;/p&gt;

&lt;p&gt;If your job never feels like a grind, maybe you are lucky. But maybe not. Grind, in some form, is inevitable. The real question is -- how do you face it without burning out or dropping the ball?&lt;/p&gt;

&lt;h2&gt;
  
  
  I wanted to quit around Day 15
&lt;/h2&gt;

&lt;p&gt;I distinctly remember telling myself: "Enough. This is too stressful." But I kept going. It was doable -- and I am glad I resisted the urge to quit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaks help with accountability
&lt;/h2&gt;

&lt;p&gt;There is something motivating about not breaking the chain. It is visual, tangible and slightly addictive in a way, and I hope it is in a good way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constraints trigger creativity
&lt;/h2&gt;

&lt;p&gt;Do not get me wrong -- I am not claiming to be a creative person.&lt;/p&gt;

&lt;p&gt;But a daily deadline forces you to think faster, write leaner, and sometimes take unusual angles. That is a good thing. Constraints are not limits -- they are frameworks.&lt;/p&gt;

&lt;p&gt;More about constraints in a future post!&lt;/p&gt;

&lt;h2&gt;
  
  
  It is possible to write a blog post in less than 2 hours
&lt;/h2&gt;

&lt;p&gt;It depends on the topic, the depth, and how prepared I am. My motivation was twofold:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The key is to just start.&lt;/li&gt;
&lt;li&gt; Not every post needs to be perfect, long or deep.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And it worked for me. I wrote some posts in under an hour, and mostly in less than two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quantity over quality
&lt;/h2&gt;

&lt;p&gt;That perfectionism kept me from blogging for the longest time. The &lt;a href="https://thenegation.com/posts/blogging-challenge/" rel="noopener noreferrer"&gt;August&lt;/a&gt; exercise helped me to overcome that. I was told that "quantity leads to quality". That might sound counterintuitive, but it is true.&lt;/p&gt;

&lt;p&gt;As an old drummer, I can tell that the more you practice, and the more grinding the practice is, the better you get.&lt;/p&gt;

&lt;p&gt;Why should writing be any different?&lt;/p&gt;

&lt;p&gt;My motto was: &lt;em&gt;just hit publish&lt;/em&gt;. Not everything needs to be perfect. So, I learned that letting go is a skill that needs to be practiced too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing helps me learn
&lt;/h2&gt;

&lt;p&gt;Often, I catch myself saying, "Wait, is that even true?" It made me go and verify lots of things this month.&lt;/p&gt;

&lt;p&gt;I suppose that this is normal: writing requires clarity and clarity requires learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crossposting revealed what people actually read
&lt;/h2&gt;

&lt;p&gt;I post to &lt;a href="https://dev.to/vst"&gt;dev.to&lt;/a&gt; and &lt;a href="https://thenegation.hashnode.dev" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;. Over time, I am starting to understand which posts resonate and which do not.&lt;/p&gt;

&lt;p&gt;I also have a clearer sense of what I enjoy writing about.&lt;/p&gt;

&lt;h2&gt;
  
  
  I now have a content archive
&lt;/h2&gt;

&lt;p&gt;A month of blog posts is not just a streak -- it is a body of work, regardless of quality. I can build on these or turn them into something more later.&lt;/p&gt;

&lt;h2&gt;
  
  
  I showed up for myself
&lt;/h2&gt;

&lt;p&gt;In the end, this challenge was a commitment to myself. I kept that promise. That builds confidence and internal trust -- something no metric can measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;After my first challenge, I told myself that I should write occasionally, which I did not. That was a mistake.&lt;/p&gt;

&lt;p&gt;Now, I should really try to keep the momentum going.&lt;/p&gt;

&lt;p&gt;Thinking of trying your own challenge? Start small. You might just learn more than you expect.&lt;/p&gt;

</description>
      <category>challenge</category>
      <category>writing</category>
      <category>blog</category>
    </item>
    <item>
      <title>Daily Z Reports and Weekly Reviews</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Fri, 30 May 2025 16:53:33 +0000</pubDate>
      <link>https://forem.com/vst/daily-z-reports-and-weekly-reviews-4gmk</link>
      <guid>https://forem.com/vst/daily-z-reports-and-weekly-reviews-4gmk</guid>
      <description>&lt;p&gt;This is not about improving productivity, but rather a simple but bottom-up hack to improve personal awareness and accountability and extend it to the team. Productivity should be a natural byproduct of that.&lt;/p&gt;

&lt;p&gt;It is important how we plan, but more important is how we execute and adapt to the ever-changing environment.&lt;/p&gt;

&lt;p&gt;Any plan without operational touch is just a wish list. Any day full of doing but lacking clear direction is just another wasted one.&lt;/p&gt;

&lt;p&gt;Which one follows which? It is a chicken-and-egg situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Z Report
&lt;/h2&gt;

&lt;p&gt;A long time ago, my business partner took a month off. Not that he asked for it, but I sent him an email every single day with a list of things I did or that came up during the day.&lt;/p&gt;

&lt;p&gt;Forget the value of the list for the business or even for him -- what I learned was that I had been floating through the day.&lt;/p&gt;

&lt;p&gt;Once I started &lt;em&gt;documenting&lt;/em&gt; the day, I realised how easy it is to derail and do the &lt;em&gt;wrong things&lt;/em&gt;. Ask me in the evening -- I worked all day. But what did I actually deliver? Nothing. I recently learned that it even has a name: &lt;em&gt;motion&lt;/em&gt; versus &lt;em&gt;action&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In point-of-sale systems, a &lt;strong&gt;Z Report&lt;/strong&gt; is the summary that closes the register at the end of the day. For me, it became my personal audit log -- a simple way to close the day with awareness.&lt;/p&gt;

&lt;p&gt;A good Z Report is not just a list of tasks. It is a reflection. A checkpoint. A mirror. It should not be complex or long, but it should be honest and clear. No need for elaborate tooling. Just a log -- something to end the day with a sense of clarity.&lt;/p&gt;

&lt;p&gt;This is how I managed to do it for a very long time without giving up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Weekly Review
&lt;/h2&gt;

&lt;p&gt;At the end of 2022, I automated the habit. A GitHub Action would open a "Daily Review" issue for me every day -- seven days a week -- with a template that changes over time. I only skipped one day! Around the same time, another GitHub Action would create a "Weekly Review and Plan" issue every Sunday.&lt;/p&gt;

&lt;p&gt;The intent was not to project-manage myself or my team. The goal was simpler: to X-ray the week. To observe patterns. To reveal where we thought we were going and where we actually landed.&lt;/p&gt;

&lt;p&gt;The first thing I noticed: the weekly plans were too ambitious. And the daily routines were too scattered. Toning down the weekly goals and dropping unimportant chores helped immediately. Energy and focus are finite. The &lt;em&gt;Weekly Review&lt;/em&gt; made that painfully obvious.&lt;/p&gt;

&lt;p&gt;Eventually, I extended the practice to the team. Each member had their own version of the daily issue. It was never mandatory. Whether written or not, the daily report helped everyone realise what was being left undone.&lt;/p&gt;

&lt;p&gt;I do not claim this made us a more productive team. But it made us a &lt;strong&gt;wiser&lt;/strong&gt; team -- one more aware of its own limitations and dynamics.&lt;/p&gt;

&lt;p&gt;We are now extending this practice to monthly and quarterly tasks and reviews, and integrating our compliance and audit practices into the same routine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflection as Operational Practice
&lt;/h2&gt;

&lt;p&gt;Together, &lt;strong&gt;Z Reports&lt;/strong&gt; and &lt;strong&gt;Weekly Reviews&lt;/strong&gt; form a valuable feedback loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Z Reports help you close the day with clarity.&lt;/li&gt;
&lt;li&gt;Weekly Reviews let you step back and adjust direction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After years of doing this, I have a better idea of where my time goes, what I keep postponing, why I fail to ship and what strategies fail to fix these.&lt;/p&gt;

&lt;p&gt;I think that it is a pretty good return on investment for a 10-minute daily review and a 30-minute weekly reflection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close the Register, Open the Week
&lt;/h2&gt;

&lt;p&gt;I do not claim that I have found the perfect productivity system. There are so many tools, methodologies and frameworks out there, some of which I have tried and many I have not. I am also aware that tools change as we change, our environment changes and our needs change, temporarily or permanently.&lt;/p&gt;

&lt;p&gt;What I do claim, however, is that self-awareness, accountability and persistence are the keys to improving your own productivity and that of your team. As tools need to change, you do not need to change the overarching principles and practices: You will still need to close the day with a Z Report and you will still need to review the week and plan the next one.&lt;/p&gt;

&lt;p&gt;That is the lifehack.&lt;/p&gt;

</description>
      <category>lifehack</category>
      <category>planning</category>
    </item>
    <item>
      <title>Cross-Compiling Haskell under NixOS with Docker</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Thu, 29 May 2025 18:37:09 +0000</pubDate>
      <link>https://forem.com/vst/cross-compiling-haskell-under-nixos-with-docker-1dhc</link>
      <guid>https://forem.com/vst/cross-compiling-haskell-under-nixos-with-docker-1dhc</guid>
      <description>&lt;p&gt;I learned how to cross-compile Haskell projects under NixOS using Docker images for ARM architectures, and how to run them under emulation on &lt;code&gt;x86_64&lt;/code&gt; hosts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I attended the &lt;a href="https://aws.amazon.com/events/summits/singapore/" rel="noopener noreferrer"&gt;AWS Summit 2025&lt;/a&gt; in Singapore. I enjoyed the event. There were booths from various companies which I found interesting, such as &lt;a href="https://about.gitlab.com/" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt; and &lt;a href="https://clickhouse.com/" rel="noopener noreferrer"&gt;ClickHouse&lt;/a&gt;. More importantly, I got to meet very interesting people.&lt;/p&gt;

&lt;p&gt;Among the booths, there was a particular one that caught my attention: AWS was showcasing their ARM-based &lt;a href="https://aws.amazon.com/ec2/graviton/" rel="noopener noreferrer"&gt;Graviton&lt;/a&gt; processors. I chatted with the AWS folks, and asked a few questions which I had in mind for quite some time.&lt;/p&gt;

&lt;p&gt;I compiled a few of my Haskell projects on my Raspberry Pi 4, which is based on the ARM architecture. I was curious to see how some others would perform on the Graviton processors. I could go and compile them on the Graviton processors, on my Raspberry Pi 4, or rather cross-compile them on my &lt;code&gt;x86_64&lt;/code&gt; workstation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-Compiling Haskell Projects
&lt;/h2&gt;

&lt;p&gt;Cross-compiling Haskell projects always seemed intimidating to me. I do not know if it is practically possible, either. Even statically linking Haskell binaries is quite a challenge, especially under Nix. Instead, I am currently statically compiling my Haskell projects under a Docker image that is built and published by &lt;a href="https://github.com/benz0li" rel="noopener noreferrer"&gt;benz0li&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/benz0li/ghc-musl" rel="noopener noreferrer"&gt;https://github.com/benz0li/ghc-musl&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am using a script that generates a &lt;code&gt;cabal.project.freeze&lt;/code&gt; from my Nix setup, compiles the project inside a Docker container from the above image, copies the binary to the host, and then compresses it using &lt;a href="https://upx.github.io/" rel="noopener noreferrer"&gt;upx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check the &lt;a href="https://github.com/vst/haskell-template-hebele/blob/ee5af52bf558c0514b482a710df497ed9fb27460/build-static.sh" rel="noopener noreferrer"&gt;script&lt;/a&gt; under my &lt;a href="https://github.com/vst/haskell-template-hebele" rel="noopener noreferrer"&gt;Haskell project template repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I knew that &lt;a href="https://github.com/benz0li" rel="noopener noreferrer"&gt;benz0li&lt;/a&gt; publishes the Docker images for both &lt;code&gt;x86_64&lt;/code&gt; and &lt;code&gt;arm64&lt;/code&gt; architectures. He has even recently published additional images to deal with the &lt;a href="https://github.com/benz0li/ghc-musl?tab=readme-ov-file#gmp-licensing-restrictions" rel="noopener noreferrer"&gt;GMP licensing restrictions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So I decided to try running the ARM-based Docker image on my &lt;code&gt;x86_64&lt;/code&gt; host, which I had never tried before. First, I needed to make sure that I can do that. This is the normal invocation of the Docker container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; quay.io/benz0li/ghc-musl:9.8.4 &lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="gp"&gt;Linux 8c14a21fc636 6.12.30 #&lt;/span&gt;1-NixOS SMP PREEMPT_DYNAMIC Thu May 22 12:29:54 UTC 2025 x86_64 Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, the &lt;code&gt;uname -a&lt;/code&gt; command ran inside the container shows that it is running on the &lt;code&gt;x86_64&lt;/code&gt; architecture. Now, we can try to run the ARM-based Docker image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/arm64 quay.io/benz0li/ghc-musl:9.8.4 &lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt; /usr/bin/uname: &lt;span class="nb"&gt;exec &lt;/span&gt;format error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring QEMU Support on NixOS
&lt;/h2&gt;

&lt;p&gt;That is expected: We cannot run an ARM-based Docker image on an &lt;code&gt;x86_64&lt;/code&gt; host without some additional setup, in particular, using &lt;a href="https://www.qemu.org/" rel="noopener noreferrer"&gt;QEMU&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Most of the tutorials I found online suggested using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--privileged&lt;/span&gt; multiarch/qemu-user-static &lt;span class="nt"&gt;--reset&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... which I decided would not work on my NixOS host. Instead, I used the NixOS option to enable QEMU emulation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;binfmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;emulatedSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"aarch64-linux"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This did not work, either. &lt;a href="https://discourse.nixos.org/t/docker-ignoring-platform-when-run-in-nixos/21120/20" rel="noopener noreferrer"&gt;Apparently&lt;/a&gt;, Docker needs the static binaries provided by the &lt;code&gt;multiarch/qemu-user-static&lt;/code&gt; image. So I changed my configuration as advised:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;boot&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;binfmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;emulatedSystems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"aarch64-linux"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nv"&gt;preferStaticEmulators&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;# Make it work with Docker&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Good News
&lt;/h2&gt;

&lt;p&gt;And it worked as such:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/arm64 quay.io/benz0li/ghc-musl:9.8.4 &lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="gp"&gt;Linux 15afb3b1a45b 6.12.30 #&lt;/span&gt;1-NixOS SMP PREEMPT_DYNAMIC Thu May 22 12:29:54 UTC 2025 aarch64 Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I could change the script to consume arbitrary arguments and pass them to the &lt;code&gt;docker run&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash build-static.sh &lt;span class="nt"&gt;--platform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux/arm64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honestly, I was not expecting it to work, but it did, although it was noticeably slower! One thing I noticed was being able to run both the &lt;code&gt;x86_64&lt;/code&gt; and &lt;code&gt;arm64&lt;/code&gt; binaries on my &lt;code&gt;x86_64&lt;/code&gt; host, which I was not expecting at all. Apparently, my system is now capable of running both architectures at the same time -- with the latter running under emulation.&lt;/p&gt;

&lt;p&gt;You can check the &lt;a href="https://github.com/vst/haskell-template-hebele/blob/ee5af52bf558c0514b482a710df497ed9fb27460/build-static.sh" rel="noopener noreferrer"&gt;script&lt;/a&gt; and adopt it for your own Haskell projects.&lt;/p&gt;

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

&lt;p&gt;It was an interesting day.&lt;/p&gt;

&lt;p&gt;Firstly, I ran a non-&lt;code&gt;x86_64&lt;/code&gt; Docker image under emulation on my &lt;code&gt;x86_64&lt;/code&gt; host, which I had never done before. Secondly, now I know that I can cross-compile my Haskell projects for ARM architectures using the &lt;code&gt;arm64&lt;/code&gt; Docker image provided by &lt;a href="https://github.com/benz0li" rel="noopener noreferrer"&gt;benz0li&lt;/a&gt;. Going forward, I can fearlessly cross-compile my Haskell projects for any supported, non-native architectures.&lt;/p&gt;

&lt;p&gt;And I am definitely going to try the Graviton processors, as soon as I spin up a Graviton EC2 instance on AWS.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>nixos</category>
      <category>haskell</category>
      <category>arm</category>
    </item>
    <item>
      <title>The Terminal State of Mind</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Wed, 28 May 2025 15:46:30 +0000</pubDate>
      <link>https://forem.com/vst/the-terminal-state-of-mind-5cgf</link>
      <guid>https://forem.com/vst/the-terminal-state-of-mind-5cgf</guid>
      <description>&lt;p&gt;I use the terminal. Not because I am a command-line wizard or particularly efficient with it, but because the GUI has never given me the rhythm and flow I need.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GUI Does Not Fit My Mind
&lt;/h2&gt;

&lt;p&gt;I have tried. I have used Gnome and KDE when they were just toddlers. I developed with both GTK and Qt, back when Qt had not yet seen Nokia coming. I know how powerful desktop environments can be. But they never worked for me the way I wanted.&lt;/p&gt;

&lt;p&gt;I do not run a desktop environment anymore. I use a window manager, a Web browser and a terminal emulator. That is all. I do not even have a file manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not a Wizard, Just a Survivor
&lt;/h2&gt;

&lt;p&gt;I envy the real terminal wizards. They juggle panes, navigate the code as if it were nothing, and configure things I did not even know were configurable. I have seen people eating escape sequences for breakfast!&lt;/p&gt;

&lt;p&gt;Me? I muddle through. For nearly 20 years, I was stuck with Emacs, not because I mastered it, but because I could never really wrap my head around &lt;code&gt;vim&lt;/code&gt; keybindings. Emacs let me use commands and functions if I could not remember the keybind. That just made sense to me.&lt;/p&gt;

&lt;p&gt;Even now, I do not consider myself a power user. There are these &lt;em&gt;modern&lt;/em&gt; tools I always wanted to add to my workflow, like &lt;code&gt;fzf&lt;/code&gt;, &lt;code&gt;ripgrep&lt;/code&gt;, &lt;code&gt;fd&lt;/code&gt;, &lt;code&gt;bat&lt;/code&gt;. I did not have the time. But recently, I find myself reaching for &lt;code&gt;bat&lt;/code&gt; and &lt;code&gt;fzf&lt;/code&gt; more and more. They reward even a shallow investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Late Than Never
&lt;/h2&gt;

&lt;p&gt;Take &lt;code&gt;tmux&lt;/code&gt;. For years I used &lt;code&gt;screen&lt;/code&gt;, mainly because it was what I knew and it worked on my remote hosts. Since I relied on tabbed terminal emulators on my workstation, I never felt a strong need for &lt;code&gt;tmux&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But eventually, I gave it a shot. And after a few days of poking around and learning just enough to do my job with it, it clicked. I am not a &lt;code&gt;tmux&lt;/code&gt; expert. I probably never will be. But even my simple understanding has made my work more comfortable.&lt;/p&gt;

&lt;p&gt;The same thing happened with &lt;code&gt;neovim&lt;/code&gt;. I used &lt;code&gt;vim&lt;/code&gt; only when I had to, usually over SSH. However, there was so much excitement around &lt;code&gt;neovim&lt;/code&gt; lately that I felt like I was missing out. So when Emacs started giving me trouble, I gave it a try. The muscle memory was not there. The command prompt felt awkward compared to Emacs. But I stuck with it, and eventually, I became productive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern Is the Same
&lt;/h2&gt;

&lt;p&gt;In both cases, the pattern repeated: a few days of grinding, a little reading, some trial and error, and then progress! Like learning a language or picking up a new instrument, there is a point where you stop struggling and start expressing.&lt;/p&gt;

&lt;p&gt;The key is patience. Once you reach that threshold of effectiveness, everything gets smoother. And the more you practice, the more efficient you become.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neurodiversity and the Terminal
&lt;/h2&gt;

&lt;p&gt;Lastly, I want to touch on a topic that I did not expect to write about.&lt;/p&gt;

&lt;p&gt;I easily get overwhelmed by visual clutter and decision fatigue. The terminal helps me quiet all that. My terminal-first setup is not about flexing or showing off, not even about efficiency in the traditional sense. It is about being less distracted. There is a calm to it.&lt;/p&gt;

&lt;p&gt;Also, the mouse! When I reach my mouse and my eyes start looking for the pointer, I get anxious: Locate, aim, and shoot! This completely breaks my flow.&lt;/p&gt;

&lt;p&gt;This got me wondering: what if the choice of terminal-first setup over more common GUI setups is not only about tradition or efficiency, but also about how the mind works?&lt;/p&gt;

&lt;p&gt;Disclaimer: I am not diagnosed with any neurodivergent condition. But I am somewhat exposed to the literature and discussions around neurodiversity, in particular dyslexia and ADHD. I do not know if what follows holds true from an expert's perspective, but I will still share my opinions.&lt;/p&gt;

&lt;p&gt;Fewer visual distractions, clear constraints and feedback, easier deep-work, and most importantly, reliance on logical and mental frameworks rather than muscle memory are all reasons why terminals might work better for people with neurodivergent conditions.&lt;/p&gt;

&lt;p&gt;Don't get me wrong, GUIs might have their own benefits, too. But I can approach TUI applications like &lt;code&gt;neovim&lt;/code&gt; or &lt;code&gt;tmux&lt;/code&gt; with a similar mindset to GUIs: Discovery is easier, multi-tasking feels more natural and transparent, and they offer better environmental control than plain terminal commands.&lt;/p&gt;

&lt;p&gt;Again, this is just my experience, but I find it interesting to consider that our choice for interfaces might be due to deeper patterns in how we think and process information.&lt;/p&gt;

&lt;p&gt;Or maybe it is much simpler; it might be just about finding a space where we can think clearly and work effectively.&lt;/p&gt;

</description>
      <category>terminal</category>
      <category>cli</category>
      <category>psychology</category>
    </item>
    <item>
      <title>NixOS Rebuilds, Upgrades and Generation Diffs</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Tue, 27 May 2025 15:31:15 +0000</pubDate>
      <link>https://forem.com/vst/nixos-rebuilds-upgrades-and-generation-diffs-2ng0</link>
      <guid>https://forem.com/vst/nixos-rebuilds-upgrades-and-generation-diffs-2ng0</guid>
      <description>&lt;p&gt;Today, I upgraded my NixOS system to the latest version, v25.05. It went smoothly. I just want to report my experience here.&lt;/p&gt;

&lt;h2&gt;
  
  
  In-Place Upgrades: NixOS vs Other Distributions
&lt;/h2&gt;

&lt;p&gt;I have been using GNU/Linux for well over two decades now. And I have never upgraded my distribution in-place. I always did a fresh install, because I did not want to deal with leftover files, changed configuration options and so on.&lt;/p&gt;

&lt;p&gt;But with NixOS, I only did fresh installs a few times across half a dozen workstations and cloud hosts, only when I provisioned them for the first time or changed my disks. The rest of the time, I just changed my NixOS channel to the latest version and ran &lt;code&gt;nixos-rebuild switch --upgrade&lt;/code&gt;. It almost always worked without a hitch. Only sometimes, I had to fix some minor issues, like deprecated options or changed package names. But I never had to reinstall my system.&lt;/p&gt;

&lt;p&gt;Today was no exception. In about half an hour, I had my system upgraded from NixOS v24.11 to v25.05.&lt;/p&gt;

&lt;h2&gt;
  
  
  NixOS Rebuilds and Diffs
&lt;/h2&gt;

&lt;p&gt;While I was upgrading, I took note of a feature I have been using for a while now: Checking the differences between the new and the old &lt;em&gt;generations&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;generation&lt;/em&gt; is a snapshot of your NixOS system at a certain point in time. This concept is quite central to both NixOS and Home Manager. It allows you to roll back to a previous state of your system if something goes wrong.&lt;/p&gt;

&lt;p&gt;These generations are stored in the &lt;code&gt;/nix/var/nix/profiles/&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-dv&lt;/span&gt; /nix/var/nix/profiles/system-&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n10&lt;/span&gt;
&lt;span class="go"&gt;/nix/var/nix/profiles/system-86-link
/nix/var/nix/profiles/system-87-link
/nix/var/nix/profiles/system-88-link
/nix/var/nix/profiles/system-89-link
/nix/var/nix/profiles/system-90-link
/nix/var/nix/profiles/system-91-link
/nix/var/nix/profiles/system-92-link
/nix/var/nix/profiles/system-93-link
/nix/var/nix/profiles/system-94-link
/nix/var/nix/profiles/system-95-link
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these directories is a symlink to a specific generation of your system, which is a derivation stored under &lt;code&gt;/nix/store/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you make even the smallest change to your NixOS configuration and run &lt;code&gt;nixos-rebuild switch&lt;/code&gt;, it will create a new generation of your system. You can then compare the current generation with the previous one using &lt;a href="https://khumba.net/projects/nvd/" rel="noopener noreferrer"&gt;nvd&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nvd diff /nix/var/nix/profiles/system-65-link /nix/var/nix/profiles/system-66-link
&lt;span class="go"&gt;&amp;lt;&amp;lt;&amp;lt; /nix/var/nix/profiles/system-65-link
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /nix/var/nix/profiles/system-66-link
&lt;span class="go"&gt;Version changes:
[ ... TRUNCATED FOR BREVITY ... ]
&lt;/span&gt;&lt;span class="gp"&gt;[U.]  #&lt;/span&gt;82  qsv                       2.2.1 -&amp;gt; 4.0.0
&lt;span class="gp"&gt;[C.]  #&lt;/span&gt;83  rofi                      1.7.5, 1.7.5+wayland3 -&amp;gt; 1.7.5+wayland3
&lt;span class="go"&gt;[ ... TRUNCATED FOR BREVITY ... ]
&lt;/span&gt;&lt;span class="gp"&gt;[C*]  #&lt;/span&gt;87  systemd                   &amp;lt;none&amp;gt;, 256.10 x2, 256.10-man, 257.3 -&amp;gt; &amp;lt;none&amp;gt;, 256.10 x2, 256.10-man, 257.5
&lt;span class="gp"&gt;[C.]  #&lt;/span&gt;88  systemd-minimal           256.10 x2, 257.3 -&amp;gt; 256.10 x2, 257.5
&lt;span class="go"&gt;[ ... TRUNCATED FOR BREVITY ... ]
Added packages:
&lt;/span&gt;&lt;span class="gp"&gt;[A.]  #&lt;/span&gt;1  bemoji                     0.4.0
&lt;span class="gp"&gt;[A.]  #&lt;/span&gt;2  ponymix                    5
&lt;span class="gp"&gt;[A.]  #&lt;/span&gt;3  rofi-bluetooth-unstable    2023-02-03
&lt;span class="gp"&gt;[A.]  #&lt;/span&gt;4  rofi-network-manager       0-unstable-2024-09-03
&lt;span class="gp"&gt;[A.]  #&lt;/span&gt;5  rofi-pulse-select          0.2.0
&lt;span class="go"&gt;Removed packages:
&lt;/span&gt;&lt;span class="gp"&gt;[R.]  #&lt;/span&gt;1  python3.12-configargparse  1.7
&lt;span class="gp"&gt;[R.]  #&lt;/span&gt;2  rofimoji                   6.5.0
&lt;span class="go"&gt;[ ... TRUNCATED FOR BREVITY ... ]
&lt;/span&gt;&lt;span class="gp"&gt;Closure size: 8610 -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;8683 &lt;span class="o"&gt;(&lt;/span&gt;1200 paths added, 1127 paths removed, delta +73, disk usage +151.4MiB&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the differences in three sections:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Version changes&lt;/strong&gt;: Packages that have been upgraded/downgraded.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Added packages&lt;/strong&gt;: New packages that have been added to the system.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Removed packages&lt;/strong&gt;: Packages that have been removed from the system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason I left &lt;code&gt;systemd&lt;/code&gt; in the sample output above is to talk about when we need a reboot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebooting After NixOS Updates
&lt;/h2&gt;

&lt;p&gt;Whether you upgrade to a new NixOS version or just upgrade your system within the same version, you might need to reboot your system if certain packages have been upgraded.&lt;/p&gt;

&lt;p&gt;When using Ubuntu, you might have noticed that the system prompts you to reboot after a kernel upgrade. This is because the package manager knows when to reboot. NixOS does not prompt you, so you have to check yourself.&lt;/p&gt;

&lt;p&gt;There is no hard and fast rule, but a good rule of thumb is to reboot if&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Linux kernel has been upgraded, or&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;systemd&lt;/code&gt; has been upgraded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The curse of NixOS is that it is a &lt;em&gt;symlink&lt;/em&gt; hell, but this is also a blessing. Check it out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;stat&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'%N'&lt;/span&gt; /run/booted-system /run/current-system
&lt;span class="gp"&gt;'/run/booted-system' -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/nix/store/9lhqy6mvqgmrisirdhbli43i5kmv3aky-nixos-system-myhost-25.05.20250525.7c43f08'&lt;/span&gt;
&lt;span class="gp"&gt;'/run/current-system' -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/nix/store/9lhqy6mvqgmrisirdhbli43i5kmv3aky-nixos-system-myhost-25.05.20250525.7c43f08'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These two symbolic links point to two derivations in the Nix store, exactly what we call &lt;em&gt;generations&lt;/em&gt; which we can list under &lt;code&gt;/nix/var/nix/profiles/&lt;/code&gt;. You can check yourself.&lt;/p&gt;

&lt;p&gt;When we run &lt;code&gt;nixos-rebuild switch&lt;/code&gt;, it will create a new generation of our system, and change the &lt;code&gt;/run/current-system&lt;/code&gt; symlink to point to the new generation. The &lt;code&gt;/run/booted-system&lt;/code&gt; symlink will still point to the previous generation, which is the one that was active when the system booted.&lt;/p&gt;

&lt;p&gt;So, if you want to check whether you need to reboot your system, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvd diff /run/booted-system /run/current-system | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'systemd|linux'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there is no output, you do not need to reboot. If there is output, you might need to reboot your system to apply the changes.&lt;/p&gt;

&lt;p&gt;There is even a tool that does this for you: &lt;a href="https://github.com/thefossguy/nixos-needsreboot" rel="noopener noreferrer"&gt;nixos-needsreboot&lt;/a&gt;. I did not feel like installing it, so I used the command above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;You can add the following to your NixOS configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;activationScripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;diffGens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      PATH=$PATH:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;makeBinPath&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nix&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      echo "----- CHANGES (ROOT) -----" | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lolcat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/lolcat -S 40&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nvd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/nvd diff /run/current-system "$systemConfig"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      echo "----- END OF CHANGES -----" | &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lolcat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/lolcat -S 40&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run the &lt;code&gt;nvd diff&lt;/code&gt; command every time you switch to a new generation of your system. It will show you the differences between the current generation and the one you are switching to. It helped me out a lot to quickly see and understand what a rebuild did to my system.&lt;/p&gt;

&lt;p&gt;I use the &lt;code&gt;lolcat&lt;/code&gt; package to colorize the two lines indicating the start and end of the &lt;code&gt;nvd diff&lt;/code&gt; command output.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;$systemConfig&lt;/code&gt; variable is a symlink to the recently built NixOS configuration, which is the one you are switching to. I had no idea that it existed until I read the &lt;a href="https://github.com/NixOS/nixpkgs/blob/62b852f6c6742134ade1abdd2a21685fd617a291/nixos/modules/system/activation/activation-script.nix#L58" rel="noopener noreferrer"&gt;source code&lt;/a&gt; of the related NixOS module.&lt;/p&gt;

&lt;p&gt;That is what I like about NixOS: Often, you can find a solution to your problem by reading the source code of the NixOS modules.&lt;/p&gt;

</description>
      <category>nixos</category>
      <category>nix</category>
      <category>linux</category>
    </item>
    <item>
      <title>Boring on Purpose: Bold Moves in Internal Tooling</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Mon, 26 May 2025 19:06:46 +0000</pubDate>
      <link>https://forem.com/vst/boring-on-purpose-bold-moves-in-internal-tooling-4emp</link>
      <guid>https://forem.com/vst/boring-on-purpose-bold-moves-in-internal-tooling-4emp</guid>
      <description>&lt;p&gt;Boring work is often the most effective work. It is the kind of work that might lead us to understand patterns and solve problems that we did not even know we had.&lt;/p&gt;

&lt;p&gt;Tolerance to boredom and tedium is a superpower, especially for programmers. Once we begin to recognize the patterns in our work, we can factor out the repetitive parts, automate them, and focus on what actually requires our attention. Tolerance is the key to such recognition in the first place.&lt;/p&gt;

&lt;p&gt;Over the past few years, I have been reflecting on how I deal with work chores -- the kind of tasks nobody volunteers for. Oddly enough, I have become more effective and efficient at doing these. One unexpected benefit has been the tools I ended up building for myself.&lt;/p&gt;

&lt;p&gt;Sometimes, these tools are not just helpful for me -- they also turn out to be useful for my colleagues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basecamp
&lt;/h2&gt;

&lt;p&gt;A few years back, I decided to replace our spreadsheet-based host and service registries with a proper, Web-based asset registry. It took us a few days to hack together a simple system that we could use to track our infrastructure elements. It was not a big project -- just a simple app backed by a database, &lt;a href="https://hasura.io" rel="noopener noreferrer"&gt;Hasura&lt;/a&gt;, and a React frontend. Since we were already using OpenID for authentication, it was easy for our team to start using it.&lt;/p&gt;

&lt;p&gt;The next immediate step was to build a CLI tool that would allow us to interact with the registry from the command line. We used Haskell for this. One of the most useful features was the ability to generate an entire SSH configuration file from the registry data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;basecamp ssh config &lt;span class="nt"&gt;--output-file&lt;/span&gt; ~/.ssh/conf.d/work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enabled us to register even less significant services in the registry -- ones we would not even bother to add to a spreadsheet. I could see quickly which services were running on which hosts, and how they related to each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving a Place to Ideas
&lt;/h2&gt;

&lt;p&gt;The name &lt;em&gt;Basecamp&lt;/em&gt; was not a coincidence. Like a real basecamp, it served as a starting point for further exploration -- not just of infrastructure, but of ideas.&lt;/p&gt;

&lt;p&gt;Since then, we have added an incident registry, a GitHub repository registry with quick stats such as the number of open issues, PRs, etc., a knowledge base, and most effectively, a simple expense tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Patterns and Relations
&lt;/h2&gt;

&lt;p&gt;After effectively using Basecamp for a while, I started to notice new patterns I could make use of. For example, I launched &lt;a href="https://hostpatrol.io" rel="noopener noreferrer"&gt;HOSTPATROL&lt;/a&gt;, a simple tool for collecting and consolidating information about hosts over SSH, and shipped &lt;a href="https://github.com/vst/clompse" rel="noopener noreferrer"&gt;clompse&lt;/a&gt;, the companion to HOSTPATROL for collecting and consolidating cloud information.&lt;/p&gt;

&lt;p&gt;As a result, I was able to run our asset registry review process in less than an hour. Even better, I changed the frequency of certain quarterly reviews to monthly, because I could do them so quickly.&lt;/p&gt;

&lt;p&gt;The best part was that we managed to reduce our monthly cloud expenses by at least 25 percent over the last year, simply by having a better overview of our infrastructure and its costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring and Bold
&lt;/h2&gt;

&lt;p&gt;This is not a life-changing, motivational story. On the contrary, it is a boring story that just happens to turn out well.&lt;/p&gt;

&lt;p&gt;It did not start with a grand vision I was invested in. It started with the reflex to replace a few spreadsheets with a simple but enabling tool.&lt;/p&gt;

&lt;p&gt;It is boring, but also bold. It is bold because spending time on internal systems goes against the grain in many organizations. And it is bold because it meant investing in boring work that nobody wants to do.&lt;/p&gt;

&lt;p&gt;The best part is that the total amount of time my team and I spent on this was already amortized within a few months -- let alone the benefits we continue to reap today.&lt;/p&gt;

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

&lt;p&gt;You might think of other alternatives to my approach of building internal tools from scratch, such as using existing SaaS solutions or tools like Notion, Airtable, headless CMS, etc.&lt;/p&gt;

&lt;p&gt;In my concept, they are the same. One tolerates boring and tedious work, recognizes patterns and relations, and builds structures that help to understand and manage work better.&lt;/p&gt;

&lt;p&gt;This kind of work is not about chasing the next big thing. It is not your next startup idea. It is about building a system that works for you and your team, makes your life easier, and gives you a foundation for new experiments. The trick is to start small -- think weekend-sized.&lt;/p&gt;

&lt;p&gt;Scratching your own itch can be risky: your use case may not generalize. But who knows, maybe someday those experiments turn into something others want to use as well. What you need is a good &lt;a href="https://momtestbook.com" rel="noopener noreferrer"&gt;Mom Test&lt;/a&gt; to validate your problem and its solution.&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>A Glimpse into My Shell</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Sun, 25 May 2025 18:34:39 +0000</pubDate>
      <link>https://forem.com/vst/a-glimpse-into-my-shell-121i</link>
      <guid>https://forem.com/vst/a-glimpse-into-my-shell-121i</guid>
      <description>&lt;p&gt;A glimpse into my shell history reveals the tools I rely on daily. These are not curated -- just raw, frequent commands logged over time.&lt;/p&gt;

&lt;p&gt;Since I have exhausted all my curiosity, I ran a quick command on my terminal to see which commands I use the most:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;history &lt;/span&gt;0 |
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; |
  &lt;span class="nb"&gt;sort&lt;/span&gt; |
  &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; |
  &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; |
  &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 25 |
  &lt;span class="nb"&gt;tac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the results:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    756 ls
    683 ssh
    596 gh
    507 git
    436 watson
    413 vim
    412 cat
    398 cd
    343 nix
    276 docker
    166 bash
    141 direnv
    139 ruff
    125 nox
    125 find
    123 curl
     93 nix-shell
     92 sudo
     92 devcli
     92 basecamp
     89 zenpolicy
     87 npm
     73 vst-nixos-switch
     71 cabal
     70 mv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If I were a carpenter, would these be my tools? Not really. I practically live inside my terminal, where I run &lt;code&gt;tmux&lt;/code&gt; and &lt;code&gt;vim&lt;/code&gt; inside it, probably half a dozen at a time. Inside &lt;code&gt;vim&lt;/code&gt;, I use quite a few plugins, most notably &lt;code&gt;neogit&lt;/code&gt;, the &lt;code&gt;magit&lt;/code&gt; replacement for &lt;code&gt;vim&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Still, these 25 commands capture how I interact with my system or what I do on my projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Navigation
&lt;/h2&gt;

&lt;p&gt;I do not have a GUI-based file manager on my system. Both &lt;code&gt;ls (756)&lt;/code&gt; and &lt;code&gt;cd (398)&lt;/code&gt; are my primary tools for navigating the filesystem.&lt;/p&gt;

&lt;p&gt;Another function of &lt;code&gt;ls&lt;/code&gt; is that it is my fidget spinner. I use it to do nothing while I am thinking about what to do next.&lt;/p&gt;

&lt;p&gt;I am not using any &lt;code&gt;zsh&lt;/code&gt; plugins like &lt;code&gt;z&lt;/code&gt; or &lt;code&gt;autojump&lt;/code&gt;. I prefer to use &lt;code&gt;cd&lt;/code&gt; and &lt;code&gt;ls&lt;/code&gt; explicitly. Maybe I should give them a try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Tools
&lt;/h2&gt;

&lt;p&gt;I record my life in &lt;code&gt;git (507)&lt;/code&gt;. Since I am using GitHub -- not completely willingly -- I use &lt;code&gt;gh (596)&lt;/code&gt; quite often, but not with stock commands only. I have quite a few aliases and extensions, including &lt;code&gt;gh watson&lt;/code&gt; to track my time in GitHub repositories.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vim&lt;/code&gt; is not a surprise here. It has been almost a year since I switched to &lt;code&gt;neovim&lt;/code&gt; from Emacs.&lt;/p&gt;

&lt;p&gt;Another core tool is &lt;code&gt;nix (343)&lt;/code&gt; and &lt;code&gt;nix-shell (93)&lt;/code&gt;. I use Nix to manage both my system and development environments. &lt;code&gt;direnv (141)&lt;/code&gt; helps me to enter the Nix development environment automatically when I &lt;code&gt;cd&lt;/code&gt; into a directory with a &lt;code&gt;.envrc&lt;/code&gt; file. And &lt;code&gt;vst-nixos-switch (73)&lt;/code&gt; is a custom command I use to deploy my NixOS system.&lt;/p&gt;

&lt;p&gt;I am quite surprised that I am using &lt;code&gt;docker (276)&lt;/code&gt; so much. I used to depend on Docker a lot on my workstation, but I have been using it less and less lately. Well, not less enough to drop it from the top 25 commands, though.&lt;/p&gt;

&lt;p&gt;Also, &lt;code&gt;ssh (683)&lt;/code&gt; is my only tool for remote access. So, it is quite normal to see it here. One thing I do with &lt;code&gt;ssh&lt;/code&gt; is to run commands on remote hosts without falling into an interactive shell, hence the high count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Programming Tools
&lt;/h2&gt;

&lt;p&gt;These commands tell quite a bit about my work, I guess.&lt;/p&gt;

&lt;p&gt;Recently, I have been doing quite a bit of Python development. In particular, I have been replacing &lt;code&gt;flake8&lt;/code&gt;, &lt;code&gt;isort&lt;/code&gt;, and &lt;code&gt;pylint&lt;/code&gt; with &lt;code&gt;ruff (139)&lt;/code&gt;. &lt;code&gt;nox (125)&lt;/code&gt; is my primary tool for running Python tests. So, the concentration of Python-related commands is not surprising.&lt;/p&gt;

&lt;p&gt;It is like blue-collar work for me, but I am still developing with JavaScript and TypeScript. I use &lt;code&gt;npm (87)&lt;/code&gt; quite often. Nope, I am not using &lt;code&gt;yarn&lt;/code&gt; or &lt;code&gt;pnpm&lt;/code&gt;. I do not even know why &lt;code&gt;pnpm&lt;/code&gt; exists and I do not want to know.&lt;/p&gt;

&lt;p&gt;Although I usually provision a &lt;code&gt;devcli (92)&lt;/code&gt; script for each project, I still run &lt;code&gt;cabal (71)&lt;/code&gt; quite often. Unlike other programming languages, I do not keep running a program to see if it works in Haskell. There are times when I keep programming for hours, and then I run &lt;code&gt;cabal test&lt;/code&gt; for performing doctests or &lt;code&gt;cabal dev-test-build -c&lt;/code&gt; to build, test, lint and format the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Utilities
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cat (412)&lt;/code&gt; and &lt;code&gt;find (125)&lt;/code&gt; did not really surprise me. I use &lt;code&gt;cat&lt;/code&gt; usually to pipe the contents of one or more files to another command, not just printing them to the terminal. I use &lt;code&gt;find&lt;/code&gt; to search for files, but almost half of the time, I perform an &lt;code&gt;exec&lt;/code&gt; or &lt;code&gt;xargs&lt;/code&gt; operation on the found files or directories.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mv (70)&lt;/code&gt; is my primary tool for moving or renaming files, except when I am working on a project inside &lt;code&gt;neovim&lt;/code&gt;, where I use the sidebar to move files around.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo (92)&lt;/code&gt; is also quite normal, because I am my own boss.&lt;/p&gt;

&lt;p&gt;Finally, &lt;code&gt;bash (166)&lt;/code&gt; is to be expected, because I use it to run shell scripts which I quickly write to automate some tasks which do not fit into a one-liner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Stuff
&lt;/h2&gt;

&lt;p&gt;I track my time with &lt;code&gt;watson (436)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;basecamp (92)&lt;/code&gt; is a custom tool my team and I wrote to keep track of incidents, notes, announcements, server and service registries, etc.&lt;/p&gt;

&lt;p&gt;And finally, &lt;code&gt;zenpolicy (89)&lt;/code&gt; is something from my kitchen. It will take some time, but hopefully, you will know more about it when I ship it.&lt;/p&gt;

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

&lt;p&gt;So, how do these numbers look?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;history &lt;/span&gt;0 |
&lt;span class="gp"&gt;    awk '{print $&lt;/span&gt;2&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;' |
&lt;/span&gt;&lt;span class="go"&gt;    sort |
    uniq -c |
    sort -n |
    tail -n 25 |
    tac |
&lt;/span&gt;&lt;span class="gp"&gt;    awk '{printf "%s,%d\n", $&lt;/span&gt;2, &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;' |
&lt;/span&gt;&lt;span class="go"&gt;    uplot barplot -d, -o /dev/stdout -t "Top 25 Commands"
                                 Top 25 Commands
                    ┌                                        ┐
                 ls ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 757.0
                ssh ┤■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 683.0
                 gh ┤■■■■■■■■■■■■■■■■■■■■■■■■■■ 596.0
                git ┤■■■■■■■■■■■■■■■■■■■■■■ 507.0
             watson ┤■■■■■■■■■■■■■■■■■■■ 436.0
                vim ┤■■■■■■■■■■■■■■■■■■ 413.0
                cat ┤■■■■■■■■■■■■■■■■■■ 412.0
                 cd ┤■■■■■■■■■■■■■■■■■ 398.0
                nix ┤■■■■■■■■■■■■■■■ 343.0
             docker ┤■■■■■■■■■■■■ 276.0
               bash ┤■■■■■■■ 166.0
             direnv ┤■■■■■■ 141.0
               ruff ┤■■■■■■ 139.0
                nox ┤■■■■■ 125.0
               find ┤■■■■■ 125.0
               curl ┤■■■■■ 124.0
            history ┤■■■■■ 104.0
          nix-shell ┤■■■■ 93.0
               sudo ┤■■■■ 92.0
             devcli ┤■■■■ 92.0
           basecamp ┤■■■■ 92.0
          zenpolicy ┤■■■■ 89.0
                npm ┤■■■■ 87.0
   vst-nixos-switch ┤■■■ 73.0
              cabal ┤■■■ 71.0
                    └                                        ┘
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your shell history never lies. Mine reflects how I like it: simple interfaces, power tools, and workflows built around the CLI. If I use something hundreds of times a week, it must be a good, reliable investment.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>shell</category>
      <category>zsh</category>
    </item>
    <item>
      <title>Data Definitions, Not Flowcharts</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Sat, 24 May 2025 19:38:37 +0000</pubDate>
      <link>https://forem.com/vst/data-definitions-not-flowcharts-48bf</link>
      <guid>https://forem.com/vst/data-definitions-not-flowcharts-48bf</guid>
      <description>&lt;p&gt;Computer programs are best understood in terms of the data they consume, process and produce. Flowcharts are useful for visualizing control flow, but not for understanding complexity.&lt;/p&gt;

&lt;p&gt;One of the most famous quotes of &lt;a href="https://en.wikipedia.org/wiki/Frederick_P._Brooks_Jr." rel="noopener noreferrer"&gt;Frederick Brooks&lt;/a&gt; is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won't usually need your flowcharts; they'll be obvious.&lt;/p&gt;

&lt;p&gt;-- &lt;em&gt;The Mythical Man-Month&lt;/em&gt;, 1975&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This quote is my constant reminder that computer programs are best understood in terms of the data they consume, process and produce. The information that a flowchart provides is not only too abstract, but also vague and underspecified even as a conceptual model.&lt;/p&gt;

&lt;p&gt;Defining what a computer program is deserves its own blog post, but my point here is that it is NOT primarily about the procedures and control flows, but rather the data structures and their relationships.&lt;/p&gt;

&lt;p&gt;I keep emphasizing that data definitions are not only important for the implementation of a program, but also for its design and documentation. It is the only connection to the phase of problem analysis: The more precisely the data is defined during analysis, the more precise the implementation will be.&lt;/p&gt;

&lt;p&gt;Although I enjoy doodling diagrams, I never bring them to the fight. Instead, I sit down and write down the data types and their relationships in a programming language.&lt;/p&gt;

&lt;p&gt;One benefit of using high-level, strongly typed functional programming languages is that &lt;em&gt;functions&lt;/em&gt; and &lt;em&gt;effects&lt;/em&gt; can be expressed in terms of type definitions: Function signatures and effect constraints are type definitions, and so on. You can even use defunctionalization to express &lt;em&gt;functions&lt;/em&gt; as data definitions, which is a common technique in functional programming.&lt;/p&gt;

&lt;p&gt;In Haskell, I usually create a module named &lt;code&gt;Algebra&lt;/code&gt; and tinker with the definitions there. I still like to refer to my prototype as an &lt;em&gt;algebra&lt;/em&gt;, a habit from when I was studying algebraic data types and free monads. How it is implemented (interpreted) is not important at this point.&lt;/p&gt;

&lt;p&gt;What is wrong with &lt;em&gt;flowcharts&lt;/em&gt;? Nothing, but they are not the right tool for analyzing and designing a program. They are useful for visualizing the control flow of a program, but not for understanding its complexity. When it comes to communicating the high-level view of a program or a system to a complete non-technical audience, I still use &lt;em&gt;flowcharts&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, I have been thinking about how &lt;a href="https://en.wikipedia.org/wiki/Arrow_(functional_programming)" rel="noopener noreferrer"&gt;arrows&lt;/a&gt; can be used to describe a high-level view of a program. Inspired by category theory, they offer a way to represent computation (that is, that transforms input of type &lt;code&gt;A&lt;/code&gt; into output of type &lt;code&gt;B&lt;/code&gt;), abstracting away internal implementation details while still focusing on inputs and outputs.&lt;/p&gt;

&lt;p&gt;This focus on data transformations is why I am exploring arrows as a design tool -- they force you to think in terms of data type transformations rather than procedural steps.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Magic JSON in Haskell</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Fri, 23 May 2025 15:15:04 +0000</pubDate>
      <link>https://forem.com/vst/magic-json-in-haskell-1me1</link>
      <guid>https://forem.com/vst/magic-json-in-haskell-1me1</guid>
      <description>&lt;p&gt;Among all Haskell libraries I have used, the one I reach for the most is &lt;a href="https://hackage.haskell.org/package/autodocodec" rel="noopener noreferrer"&gt;autodocodec&lt;/a&gt;. I will explain what it is and what freebies it gives you.&lt;/p&gt;

&lt;p&gt;I have been thinking about writing this post for a while, but I never got around to it. There is no right time to talk about it, so it is now.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON in Haskell
&lt;/h2&gt;

&lt;p&gt;It is almost too simple. &lt;a href="https://hackage.haskell.org/package/aeson" rel="noopener noreferrer"&gt;aeson&lt;/a&gt; library, the most popular JSON library in Haskell, defines a JSON value as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;Value&lt;/span&gt;
  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Object&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Array&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;Array&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;Text&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Number&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;Scientific&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Haskell data types can have JSON value representations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MkPost&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;postTitle&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;ToJSON&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;toJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;object&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"title"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
      &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
      &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"published"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;FromJSON&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;parseJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;withObject&lt;/span&gt; &lt;span class="s"&gt;"Post"&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;MkPost&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;.:&lt;/span&gt; &lt;span class="s"&gt;"title"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;.:&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;.:&lt;/span&gt; &lt;span class="s"&gt;"published"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, we can work with the JSON representation of our data types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"published"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;This is a very short post.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:true,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Maybe&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;
&lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;postTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are coming from JavaScript or TypeScript, this is what you are used to do with &lt;code&gt;JSON.parse&lt;/code&gt; and &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;zod&lt;/a&gt;. And you are right, in Haskell, this is quite verbose and boilerplate-driven. But it is quite principled and type-safe. For example, you can parse and map, but cannot perform &lt;code&gt;IO&lt;/code&gt; operations when defining your &lt;code&gt;ToJSON&lt;/code&gt; and &lt;code&gt;FromJSON&lt;/code&gt; instances. Also, you cannot have multiple JSON representations of the same data type (so-called type-class instance coherence in Haskell). You need &lt;a href="https://wiki.haskell.org/Newtype" rel="noopener noreferrer"&gt;newtype&lt;/a&gt; wrappers for that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;newtype&lt;/span&gt; &lt;span class="kt"&gt;QuickPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MkQuickPost&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;
  &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;ToJSON&lt;/span&gt; &lt;span class="kt"&gt;QuickPost&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;toJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkQuickPost&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;object&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"subject"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
      &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"body"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
      &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"published"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;FromJSON&lt;/span&gt; &lt;span class="kt"&gt;QuickPost&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;parseJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;withObject&lt;/span&gt; &lt;span class="s"&gt;"QuickPost"&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;MkQuickPost&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;.:&lt;/span&gt; &lt;span class="s"&gt;"subject"&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;.:&lt;/span&gt; &lt;span class="s"&gt;"body"&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;.:&lt;/span&gt; &lt;span class="s"&gt;"published"&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are referring to the same data type, but with a different JSON representation, without any runtime overhead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkQuickPost&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"body"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"published"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"subject"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;This is a very short post.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:true,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Maybe&lt;/span&gt; &lt;span class="kt"&gt;QuickPost&lt;/span&gt;
&lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkQuickPost&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;postTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JSON Field Order
&lt;/h2&gt;

&lt;p&gt;You might have noticed that the order of fields in the JSON output is not the same as the order of fields in the Haskell data type. This is because &lt;a href="https://hackage.haskell.org/package/aeson" rel="noopener noreferrer"&gt;aeson&lt;/a&gt; does not guarantee the order of fields in the JSON output if you define &lt;code&gt;ToJSON&lt;/code&gt; without &lt;code&gt;toEncoding&lt;/code&gt;. &lt;code&gt;toEncoding&lt;/code&gt; would be a more efficient and predictable way to define how we output the JSON representation. If you do not define it, &lt;a href="https://hackage.haskell.org/package/aeson" rel="noopener noreferrer"&gt;aeson&lt;/a&gt; will use &lt;code&gt;toJSON&lt;/code&gt; to convert the data type to a JSON &lt;code&gt;Value&lt;/code&gt;, and use &lt;code&gt;Value&lt;/code&gt;'s &lt;code&gt;toEncoding&lt;/code&gt; to output the JSON. This is when the order of fields is based on what is pulled from the &lt;code&gt;Map&lt;/code&gt; in the &lt;code&gt;Object&lt;/code&gt; constructor, which is not necessarily the same as the order of fields in the Haskell data type.&lt;/p&gt;

&lt;p&gt;Let us try to define &lt;code&gt;toEncoding&lt;/code&gt; for our &lt;code&gt;Post&lt;/code&gt; data type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;ToJSON&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;toJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;object&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"title"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
      &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
      &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"published"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;toEncoding&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;pairs&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;  &lt;span class="s"&gt;"title"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"published"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;published&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"published"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What if I am lazy?
&lt;/h2&gt;

&lt;p&gt;If you are lazy enough, you will not want to define &lt;code&gt;ToJSON&lt;/code&gt; and &lt;code&gt;FromJSON&lt;/code&gt; instances for your data types manually. You will want to use &lt;a href="https://wiki.haskell.org/Generics" rel="noopener noreferrer"&gt;Generic&lt;/a&gt; or &lt;a href="https://wiki.haskell.org/Template_Haskell" rel="noopener noreferrer"&gt;TemplateHaskell&lt;/a&gt; to derive them automatically.&lt;/p&gt;

&lt;p&gt;I have used the &lt;code&gt;Generic&lt;/code&gt; approach for a very long time. It is quite simple and you might even want to centralize your JSON style to enforce it across your data types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MkPost&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;postTitle&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;ToJSON&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;toEncoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genericToEncoding&lt;/span&gt; &lt;span class="n"&gt;defaultOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fieldLabelModifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;camelTo2&lt;/span&gt; &lt;span class="sc"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;FromJSON&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;parseJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genericParseJSON&lt;/span&gt; &lt;span class="n"&gt;defaultOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fieldLabelModifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;camelTo2&lt;/span&gt; &lt;span class="sc"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... which will give us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"published"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;This is a very short post.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:true,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Maybe&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;
&lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;postTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not worry much about the &lt;code&gt;fieldLabelModifier&lt;/code&gt; function. You can create your own &lt;code&gt;options&lt;/code&gt; generator that would work for all your data types as long as you tell it how to drop the prefix from the field names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="n"&gt;myOptions&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Options&lt;/span&gt;
&lt;span class="n"&gt;myOptions&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defaultOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fieldLabelModifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;camelTo2&lt;/span&gt; &lt;span class="sc"&gt;'_'&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;ToJSON&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;toEncoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genericToEncoding&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myOptions&lt;/span&gt; &lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;FromJSON&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;parseJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genericParseJSON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myOptions&lt;/span&gt; &lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What if I am even lazier?
&lt;/h2&gt;

&lt;p&gt;OK, if you have not used Haskell before, by now, you might have already given up. But this approach of defining type-class instances is quite common in Haskell. They often involve boilerplate, but they provide significant safety and discipline.&lt;/p&gt;

&lt;p&gt;What if we want to generate the YAML representations? How about JSON schema or OpenAPI schema?&lt;/p&gt;

&lt;p&gt;They all have their own type-classes (&lt;code&gt;ToSchema&lt;/code&gt;) or piggyback on the existing ones (for example, &lt;a href="https://hackage.haskell.org/package/yaml" rel="noopener noreferrer"&gt;yaml&lt;/a&gt; uses &lt;a href="https://hackage.haskell.org/package/aeson" rel="noopener noreferrer"&gt;aeson&lt;/a&gt; for decoding and encoding).&lt;/p&gt;

&lt;p&gt;This is where &lt;a href="https://hackage.haskell.org/package/autodocodec" rel="noopener noreferrer"&gt;autodocodec&lt;/a&gt; comes in. It is a library that allows you to automatically derive instances such as &lt;code&gt;ToJSON&lt;/code&gt;, &lt;code&gt;FromJSON&lt;/code&gt;, &lt;code&gt;ToSchema&lt;/code&gt; and &lt;code&gt;FromSchema&lt;/code&gt;. You will write the boilerplate once, and then you will be able to generate the rest of the instances automatically.&lt;/p&gt;

&lt;p&gt;Let us reproduce the previous example using &lt;a href="https://hackage.haskell.org/package/autodocodec" rel="noopener noreferrer"&gt;autodocodec&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MkPost&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;postTitle&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Text&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Generic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Eq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kr"&gt;deriving&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FromJSON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;ToJSON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Autodocodec&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="kr"&gt;instance&lt;/span&gt; &lt;span class="kt"&gt;HasCodec&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="n"&gt;codec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;object&lt;/span&gt; &lt;span class="s"&gt;"Post"&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;
      &lt;span class="kt"&gt;MkPost&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;$&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;requiredField&lt;/span&gt; &lt;span class="s"&gt;"title"&lt;/span&gt; &lt;span class="s"&gt;"Title of the post"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;postTitle&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;requiredField&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="s"&gt;"Content of the post"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;*&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;requiredField&lt;/span&gt; &lt;span class="s"&gt;"published"&lt;/span&gt; &lt;span class="s"&gt;"Publish status of the post"&lt;/span&gt; &lt;span class="o"&gt;.=&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"published"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;This is a very short post.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:true,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}"&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Maybe&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;
&lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;MkPost&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;postTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"This is a very short post."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;postPublished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We wrote more, but did we get more? Yes, we did. Look at this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; Data.Text.IO.putStrLn &lt;span class="nv"&gt;$ &lt;/span&gt;renderColouredSchemaViaCodec @Post
&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Post
&lt;span class="gp"&gt;title: #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;required
&lt;span class="gp"&gt;  #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Title of the post
&lt;span class="gp"&gt;  &amp;lt;string&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;content: #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;required
&lt;span class="gp"&gt;  #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Content of the post
&lt;span class="gp"&gt;  &amp;lt;string&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;published: #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;required
&lt;span class="gp"&gt;  #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Publish status of the post
&lt;span class="gp"&gt;  &amp;lt;boolean&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the help of &lt;code&gt;renderColouredSchemaViaCodec&lt;/code&gt; function from &lt;a href="https://hackage.haskell.org/package/autodocodec-yaml" rel="noopener noreferrer"&gt;autodocodec-yaml&lt;/a&gt;, we can generate a coloured schema for our data type. Cool, right?&lt;/p&gt;

&lt;p&gt;Let us do more, with the help of &lt;code&gt;jsonSchemaViaCodec&lt;/code&gt; function from &lt;a href="https://hackage.haskell.org/package/autodocodec-schema" rel="noopener noreferrer"&gt;autodocodec-schema&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt; &lt;span class="n"&gt;jsonSchemaViaCodec&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="kt"&gt;Post&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s"&gt;"$comment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"properties"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"$comment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Content of the post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"published"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"$comment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Publish status of the post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"boolean"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;"title"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"$comment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Title of the post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"string"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="s"&gt;"required"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"published"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"title"&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"object"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I really like &lt;a href="https://hackage.haskell.org/package/autodocodec" rel="noopener noreferrer"&gt;autodocodec&lt;/a&gt;. I am using it heavily in my projects, especially when I need to generate JSON Schema (for example, to aid configuration file specifications) or OpenAPIv3 Schema (for &lt;a href="https://hackage.haskell.org/package/servant" rel="noopener noreferrer"&gt;Servant&lt;/a&gt; projects). I recently noticed that there is even &lt;a href="https://hackage.haskell.org/package/autodocodec-nix" rel="noopener noreferrer"&gt;autodocodec-nix&lt;/a&gt; as an interpreter for Nix expressions.&lt;/p&gt;

&lt;p&gt;I am fine with writing boilerplate as long as it gives me type-safety and discipline. Using &lt;a href="https://hackage.haskell.org/package/autodocodec" rel="noopener noreferrer"&gt;autodocodec&lt;/a&gt;, I feel like maximizing my return on investment into writing boilerplate.&lt;/p&gt;

</description>
      <category>haskell</category>
      <category>json</category>
      <category>openapi</category>
    </item>
    <item>
      <title>Hacking Haskell with Nix: Two Tricks</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Thu, 22 May 2025 15:44:44 +0000</pubDate>
      <link>https://forem.com/vst/hacking-haskell-with-nix-two-tricks-55ea</link>
      <guid>https://forem.com/vst/hacking-haskell-with-nix-two-tricks-55ea</guid>
      <description>&lt;p&gt;I have mentioned a few times in my posts that &lt;a href="https://www.haskell.org/" rel="noopener noreferrer"&gt;Haskell&lt;/a&gt; is my go-to language. This is true even for small applications which go beyond a simple shell script.&lt;/p&gt;

&lt;p&gt;Hacking Haskell with Nix is an easy and fun way to quickly prototype. I want to share two tricks that I use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nix-Based Haskell Scripting
&lt;/h2&gt;

&lt;p&gt;If you are using &lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;Nix&lt;/a&gt;, you may have heard of &lt;a href="https://wiki.nixos.org/wiki/Nix-shell_shebang" rel="noopener noreferrer"&gt;Nix-Shell Shebang&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env nix-shell
#!nix-shell -i python3 -p python3 -p python3Packages.click
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;

&lt;span class="nd"&gt;@click.command&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nd"&gt;@click.option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Number of greetings.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@click.option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Your name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The person to greet.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Simple program that greets NAME for a total of COUNT times.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can &lt;code&gt;chmod +x script.py&lt;/code&gt; and run it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./script.py &lt;span class="nt"&gt;--count&lt;/span&gt; 3 &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"John Doe"&lt;/span&gt;
&lt;span class="go"&gt;Hello John Doe!
Hello John Doe!
Hello John Doe!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a brilliant hack allowing you to not only pick the interpreter for your script, but also add dependencies to the shebang line.&lt;/p&gt;

&lt;p&gt;Let us see how the same approach works with Haskell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="o"&gt;#!/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;nix&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;
&lt;span class="o"&gt;#!&lt;/span&gt;&lt;span class="n"&gt;nix&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="n"&gt;runghc&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="s"&gt;"haskellPackages.ghcWithPackages (hask: [ hask.aeson hask.bytestring ])"&lt;/span&gt;

&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="k"&gt;qualified&lt;/span&gt; &lt;span class="nn"&gt;Data.Aeson&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Aeson&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="k"&gt;qualified&lt;/span&gt; &lt;span class="nn"&gt;Data.ByteString.Lazy.Char8&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;BLC&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;System.Exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;IO&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kt"&gt;BLC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getContents&lt;/span&gt;
  &lt;span class="kr"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt; &lt;span class="kr"&gt;of&lt;/span&gt;
    &lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="kt"&gt;Nothing&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;die&lt;/span&gt; &lt;span class="s"&gt;"Invalid JSON!"&lt;/span&gt;

&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;IO&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is an object."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Array&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is an array."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is a string."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Number&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is a number."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is a boolean."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is null."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same as before, you can &lt;code&gt;chmod +x script.hs&lt;/code&gt; and run it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; | ./script.hs
&lt;span class="go"&gt;JSON value is an object.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Granted, this approach does not scale well, as you must manually pull in all development dependencies into a Nix shell, such as &lt;code&gt;cabal&lt;/code&gt;, &lt;code&gt;haskell-language-server&lt;/code&gt;, &lt;code&gt;fourmolu&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;If that is the case, why not use a proper but minimal Haskell project template?&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimal Haskell Project Template
&lt;/h2&gt;

&lt;p&gt;I use &lt;code&gt;cabal&lt;/code&gt;. A simple, &lt;code&gt;cabal&lt;/code&gt;-based Haskell package needs only two files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;Main.hs&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;haskell-simple-app.cabal&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let us say that we are using the previous Haskell program, but without the Nix shebang:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="k"&gt;qualified&lt;/span&gt; &lt;span class="nn"&gt;Data.Aeson&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Aeson&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="k"&gt;qualified&lt;/span&gt; &lt;span class="nn"&gt;Data.ByteString.Lazy.Char8&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;BLC&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;System.Exit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;die&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;IO&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="kt"&gt;BLC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getContents&lt;/span&gt;
  &lt;span class="kr"&gt;case&lt;/span&gt; &lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt; &lt;span class="n"&gt;jsonData&lt;/span&gt; &lt;span class="kr"&gt;of&lt;/span&gt;
    &lt;span class="kt"&gt;Just&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="kt"&gt;Nothing&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;die&lt;/span&gt; &lt;span class="s"&gt;"Invalid JSON!"&lt;/span&gt;

&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;IO&lt;/span&gt; &lt;span class="nb"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Object&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is an object."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Array&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is an array."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is a string."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Number&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is a number."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="kr"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is a boolean."&lt;/span&gt;
&lt;span class="n"&gt;tellJsonType&lt;/span&gt; &lt;span class="kt"&gt;Aeson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;putStrLn&lt;/span&gt; &lt;span class="s"&gt;"JSON value is null."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our script depends on two libraries: &lt;code&gt;aeson&lt;/code&gt; and &lt;code&gt;bytestring&lt;/code&gt;. Then, the &lt;code&gt;cabal&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cabal-version: 2.4
name:          haskell-simple-app
version:       0.0.0.1
license:       MIT
author:        Your Name
maintainer:    you@example.com
synopsis:      Minimal single-file Haskell executable
category:      Utility
build-type:    Simple

executable haskell-simple-app
  main-is:          Main.hs
  hs-source-dirs:   .
  ghc-options:      -Wall
  build-depends:
    , aeson
    , base        &amp;gt;=4.14 &amp;amp;&amp;amp; &amp;lt;5
    , bytestring

  default-language: Haskell2010
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am not explaining the &lt;code&gt;cabal&lt;/code&gt; file here, but you should get the general idea: A set of metadata followed by an &lt;code&gt;executable&lt;/code&gt; section that describes how to build an executable with its dependencies.&lt;/p&gt;

&lt;p&gt;In addition to the above two files, how about a &lt;a href="https://wiki.nixos.org/wiki/Flakes" rel="noopener noreferrer"&gt;Nix Flakes&lt;/a&gt; to develop and package this project as a Nix program?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A Simple Application in Haskell"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixos-unstable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:numtide/flake-utils"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;eachDefaultSystem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nv"&gt;thisHaskell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskellPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;overrides&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;super&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;haskell-simple-app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;callCabal2nix&lt;/span&gt; &lt;span class="s2"&gt;"haskell-simple-app"&lt;/span&gt; &lt;span class="sx"&gt;./.&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nv"&gt;thisShell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;shellFor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell-simple-app&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="nv"&gt;withHoogle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c"&gt;## Haskell related build inputs:&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;apply-refact&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cabal-fmt&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cabal-install&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cabal2nix&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fourmolu&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell-language-server&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hlint&lt;/span&gt;

            &lt;span class="c"&gt;## Other build inputs for various development requirements:&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nil&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs-fmt&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nv"&gt;thisPackage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;justStaticExecutables&lt;/span&gt; &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell-simple-app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;in&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;haskell-simple-app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisPackage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell-simple-app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nv"&gt;devShells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisShell&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are familiar with Nix and &lt;a href="https://wiki.nixos.org/wiki/Flakes" rel="noopener noreferrer"&gt;Nix Flakes&lt;/a&gt;, this should be straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; We create our Nix Haskell package set, &lt;code&gt;thisHaskell&lt;/code&gt;, with our project &lt;code&gt;haskell-simple-app&lt;/code&gt; using &lt;code&gt;callCabal2nix&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; We create a Nix shell, &lt;code&gt;thisShell&lt;/code&gt;, with our project and a few other Haskell development tools.&lt;/li&gt;
&lt;li&gt; We create a Nix package, &lt;code&gt;thisPackage&lt;/code&gt;, with our project built as a static executable (not really statically linked, but the Nix closure cleared).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With all these three files in place, we can then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; | nix run
&lt;span class="go"&gt;JSON value is an object.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can do anything you want with this setup, including installing the program into your Nix profile and use it from anywhere on your system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nix profile &lt;span class="nb"&gt;install&lt;/span&gt; .#haskell-simple-app
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{}'&lt;/span&gt; | haskell-simple-app
&lt;span class="go"&gt;JSON value is an object.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I wanted to share with you two ways to quickly hack a Haskell script or even a minimal but complete Haskell project using Nix.&lt;/p&gt;

&lt;p&gt;I kept these as snippets here and there. Today, I added the &lt;code&gt;haskell-simple-app&lt;/code&gt; template to &lt;a href="https://github.com/vst/nix-flake-templates" rel="noopener noreferrer"&gt;my Nix Flakes Templates repository&lt;/a&gt;. I am planning to refine it further, but you can use it now to create a new Haskell project with Nix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;nix flake init --template github:vst/nix-flake-templates#&lt;/span&gt;haskell-simple-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or even run this template straight away without creating a new project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;echo '{}' | nix run "github:vst/nix-flake-templates?dir=templates/haskell-simple-app" --no-write-lock-file
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still, why would you do that instead of creating a proper project?&lt;/p&gt;

</description>
      <category>haskell</category>
      <category>nix</category>
      <category>development</category>
    </item>
    <item>
      <title>Hacking with mdBook</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Wed, 21 May 2025 15:12:50 +0000</pubDate>
      <link>https://forem.com/vst/hacking-with-mdbook-589d</link>
      <guid>https://forem.com/vst/hacking-with-mdbook-589d</guid>
      <description>&lt;p&gt;This post explores how to hack an &lt;a href="https://rust-lang.github.io/mdBook/" rel="noopener noreferrer"&gt;mdBook&lt;/a&gt; project with scripts.&lt;/p&gt;

&lt;p&gt;A few days back, I &lt;a href="https://thenegation.com/posts/migrate-from-zola-to-hugo/" rel="noopener noreferrer"&gt;wrote a blog post&lt;/a&gt; about &lt;a href="https://en.wikipedia.org/wiki/Static_site_generator" rel="noopener noreferrer"&gt;static site generators&lt;/a&gt;, in particular how I decided to migrate my blog from &lt;a href="https://www.getzola.org/" rel="noopener noreferrer"&gt;Zola&lt;/a&gt; to &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;. One of my points was to be able to &lt;em&gt;hack&lt;/em&gt; my own content before generating the final HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  mdBook
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rust-lang.github.io/mdBook/" rel="noopener noreferrer"&gt;mdBook&lt;/a&gt; is a Rust-based tool to create Web-based books from vanilla Markdown files. Although it is quite minimalistic, you will bump into it quite often in the wild. Most notably, the &lt;a href="https://doc.rust-lang.org/book/" rel="noopener noreferrer"&gt;Rust Book&lt;/a&gt; uses it. I see it quite often in the &lt;a href="https://nixos.org/" rel="noopener noreferrer"&gt;Nix&lt;/a&gt; ecosystem, too.&lt;/p&gt;

&lt;p&gt;As I said, it is minimalistic. If you have a tree of Markdown files, you just need a &lt;code&gt;SUMMARY.md&lt;/code&gt; file to render them into a navigable book. The Markdown flavor is &lt;a href="https://commonmark.org/" rel="noopener noreferrer"&gt;CommonMark&lt;/a&gt;, without any extensions and front-matter. This makes it readable and navigable even on GitHub or GitLab without compiling the book.&lt;/p&gt;

&lt;p&gt;I used &lt;code&gt;mdBook&lt;/code&gt; in the past, and I reach for it every now and then. Occasionally, my needs quickly grow bigger than what &lt;code&gt;mdBook&lt;/code&gt; offers out of the box. The good news is that I can help myself with a few scripts that pre-process my Markdown files before building the book.&lt;/p&gt;

&lt;p&gt;Today, I did it again, and I thought it would be a good idea to share my approach with you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Let us create a simple &lt;code&gt;mdBook&lt;/code&gt; project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix run nixpkgs#mdbook &lt;span class="nt"&gt;--&lt;/span&gt; init &lt;span class="nt"&gt;--title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mdBook Demo"&lt;/span&gt; &lt;span class="nt"&gt;--ignore&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory structure looks like this along with a quick overview of the files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tree
&lt;span class="c"&gt;.
&lt;/span&gt;&lt;span class="go"&gt;├── book               &amp;lt;- The output directory
├── book.toml          &amp;lt;- mdBook configuration
├── .gitignore         &amp;lt;- Files to ignore in Git tree
└── src                &amp;lt;- Content directory for Markdown files
    ├── chapter_1.md   &amp;lt;- Sample Markdown file
    └── SUMMARY.md     &amp;lt;- Sidebar navigation file

3 directories, 4 files
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are interested in the files under the &lt;code&gt;src&lt;/code&gt; directory. In particular, &lt;code&gt;src/SUMMARY.md&lt;/code&gt; file must exist and refer to individual Markdown files for them to be included in the book:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Summary&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Chapter 1&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;./chapter_1.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I faced two problems.&lt;/p&gt;

&lt;p&gt;The first problem was to manage metadata in my Markdown files. &lt;code&gt;mdBook&lt;/code&gt; does not allow any front-matter in the Markdown files. While there is a pre-processor that removes front-matter, I want to utilize the metadata before it is stripped.&lt;/p&gt;

&lt;p&gt;My second problem was me being too lazy to manage the &lt;code&gt;SUMMARY.md&lt;/code&gt; file manually. If I can put &lt;code&gt;weight&lt;/code&gt; in the front-matter of the Markdown files, I can use it to define the order of each chapter in the book. This is a common pattern in many static site generators. Indeed, I wanted to add some further metadata to Markdown files, such as &lt;em&gt;last review date&lt;/em&gt;, &lt;em&gt;tags&lt;/em&gt;, &lt;em&gt;todos&lt;/em&gt; and so on, to generate more Markdown files in addition to &lt;code&gt;SUMMARY.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Let us say we have two Markdown content files with front-matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;introduction.md&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Introduction"&lt;/span&gt;
&lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;chapter_1.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chapter&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
&lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;SUMMARY.md&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;How about parsing the front-matter, sorting the records by &lt;code&gt;weight&lt;/code&gt;, and preparing the &lt;code&gt;SUMMARY.md&lt;/code&gt; file with &lt;a href="https://mustache.github.io/" rel="noopener noreferrer"&gt;mustache&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;First, let us pull the dependencies using Nix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix-shell &lt;span class="nt"&gt;-p&lt;/span&gt; jq &lt;span class="nt"&gt;-p&lt;/span&gt; yq-go &lt;span class="nt"&gt;-p&lt;/span&gt; mustache-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need a Mustache template, &lt;code&gt;SUMMARY.mustache&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight handlebars"&gt;&lt;code&gt;# Summary

&lt;span class="k"&gt;{{#&lt;/span&gt;&lt;span class="nv"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
- [&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;](&lt;span class="k"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;)
&lt;span class="k"&gt;{{/&lt;/span&gt;&lt;span class="nv"&gt;.&lt;/span&gt;&lt;span class="k"&gt;}}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following command will generate the &lt;code&gt;SUMMARY.md&lt;/code&gt; file, running under the &lt;code&gt;./src&lt;/code&gt; directory for simplicity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-iname&lt;/span&gt; &lt;span class="s2"&gt;"*.md"&lt;/span&gt; &lt;span class="nt"&gt;-and&lt;/span&gt; &lt;span class="nt"&gt;-not&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"SUMMARY.md"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-exec&lt;/span&gt; yq &lt;span class="nt"&gt;--front-matter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;extract &lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="s1"&gt;'. += {"path": "{}"}'&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt; |
  jq &lt;span class="nt"&gt;--slurp&lt;/span&gt; &lt;span class="s2"&gt;"sort_by(.weight)"&lt;/span&gt; |
  mustache ../SUMMARY.mustache &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; SUMMARY.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We &lt;code&gt;find&lt;/code&gt; all Markdown files of interest, extract their front-matter as JSON and inject the file &lt;code&gt;path&lt;/code&gt; (relative to &lt;code&gt;src&lt;/code&gt; directory) to the JSON object, slurp individual JSON objects into an array and sort it by the &lt;code&gt;weight&lt;/code&gt; field. Finally, we use the JSON data to be rendered by the Mustache template into the &lt;code&gt;SUMMARY.md&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Done! One task is complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Individual Chapters
&lt;/h3&gt;

&lt;p&gt;Each Markdown file is now rendered with the front-matter included. Instead, we want to extract the &lt;code&gt;title&lt;/code&gt; from the front-matter, add it as an &lt;code&gt;#&lt;/code&gt; heading and remove the front-matter.&lt;/p&gt;

&lt;p&gt;This is done using the &lt;code&gt;mdBook&lt;/code&gt; &lt;a href="https://rust-lang.github.io/mdBook/for_developers/preprocessors.html" rel="noopener noreferrer"&gt;pre-processor&lt;/a&gt;. Our pre-processor is a simple Python script, added to the &lt;code&gt;book.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[preprocessor.my-preprocessor]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"python script.py"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What should the &lt;code&gt;script.py&lt;/code&gt; file look like?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;frontmatter&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;supports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;process_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chapter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;process_chapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chapter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_chapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;chapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does it do? &lt;code&gt;mdBook&lt;/code&gt; will collect all Markdown files of interest, pack them into an internal data definition (as a tuple of &lt;code&gt;[context]&lt;/code&gt; and &lt;code&gt;[book]&lt;/code&gt;), pass it to the pre-processor as a JSON value from &lt;code&gt;stdin&lt;/code&gt;, and expect the processed book data to be returned as a JSON value from &lt;code&gt;stdout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Particularly in the above case:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;mdBook&lt;/code&gt; will call our script with the &lt;code&gt;supports&lt;/code&gt; argument to check if it supports the pre-processor. We do not care about it here, so the script just returns &lt;code&gt;0&lt;/code&gt; to indicate success.&lt;/li&gt;
&lt;li&gt; Otherwise, the script reads the JSON data from &lt;code&gt;stdin&lt;/code&gt; into &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;book&lt;/code&gt; symbols.&lt;/li&gt;
&lt;li&gt; We are interested in the &lt;code&gt;book&lt;/code&gt; value which has one or more &lt;code&gt;sections&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; We iterate over each section and process it.&lt;/li&gt;
&lt;li&gt; For each section, we check if it is a chapter, and if so, process it.&lt;/li&gt;
&lt;li&gt; For each chapter, we use the Python &lt;a href="https://pypi.org/project/python-frontmatter/" rel="noopener noreferrer"&gt;frontmatter&lt;/a&gt; library to extract the front-matter and the content. If there is any front-matter, we remove it by replacing the content with the &lt;code&gt;title&lt;/code&gt; as an &lt;code&gt;h1&lt;/code&gt; heading and the rest of the content.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let us serve the book:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nix-shell &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; mdbook &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; python3Packages.python-frontmatter &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--run&lt;/span&gt; &lt;span class="s2"&gt;"mdbook serve"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done! You can now open your browser and navigate to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;I like to use &lt;code&gt;mdBook&lt;/code&gt; for quick documentation. Sometimes, I end up managing data in my Markdown files and I need to pre-process them before building the book. Likewise, I may need to generate additional Markdown files for the book, such as &lt;code&gt;SUMMARY.md&lt;/code&gt; or other files.&lt;/p&gt;

&lt;p&gt;The power of &lt;code&gt;mdBook&lt;/code&gt; is due to its simplicity and clear design for pre-processing.&lt;/p&gt;

&lt;p&gt;I strongly recommend keeping it in your toolbox, along with other command-line power tools, such as &lt;a href="https://jqlang.org/" rel="noopener noreferrer"&gt;jq&lt;/a&gt;, &lt;a href="https://mikefarah.gitbook.io/yq" rel="noopener noreferrer"&gt;yq&lt;/a&gt;, and &lt;a href="https://mustache.github.io/" rel="noopener noreferrer"&gt;mustache&lt;/a&gt;. No need to mention Nix, right?&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>mdbook</category>
      <category>markdown</category>
    </item>
    <item>
      <title>Haskell Project Template with Nix Flakes</title>
      <dc:creator>Vehbi Sinan Tunalioglu</dc:creator>
      <pubDate>Tue, 20 May 2025 15:06:04 +0000</pubDate>
      <link>https://forem.com/vst/haskell-project-template-with-nix-flakes-50pa</link>
      <guid>https://forem.com/vst/haskell-project-template-with-nix-flakes-50pa</guid>
      <description>&lt;p&gt;This post introduces my Haskell project template powered by Nix Flakes -- a simple setup I use to quickly spin up new Haskell applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.haskell.org" rel="noopener noreferrer"&gt;Haskell&lt;/a&gt; has been my go-to language for over 7 years. First, I started with &lt;a href="https://docs.haskellstack.org" rel="noopener noreferrer"&gt;Stack&lt;/a&gt;, then switched to plain &lt;a href="https://www.haskell.org/cabal" rel="noopener noreferrer"&gt;Cabal&lt;/a&gt; and finally settled on &lt;a href="https://nixos.org" rel="noopener noreferrer"&gt;Nix&lt;/a&gt; to provision a development environment for Haskell projects.&lt;/p&gt;

&lt;p&gt;Since I primarily develop applications rather than libraries, I find it more practical to structure my development environment and builds around producing standalone Nix packages, statically built executables and container images using Nix.&lt;/p&gt;

&lt;p&gt;Nix offers several approaches for working with Haskell. In my experience, the three most prominent are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;a href="https://nixos.org/manual/nixpkgs/stable/#haskell" rel="noopener noreferrer"&gt;The Haskell infrastructure in Nixpkgs&lt;/a&gt; -- simple and built-in.&lt;/li&gt;
&lt;li&gt; &lt;a href="https://input-output-hk.github.io/haskell.nix/" rel="noopener noreferrer"&gt;Haskell.nix&lt;/a&gt; -- a more flexible but complex option from &lt;a href="https://iohk.io" rel="noopener noreferrer"&gt;IOHK&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; &lt;a href="https://community.flake.parts/haskell-flake" rel="noopener noreferrer"&gt;haskell-flake&lt;/a&gt; -- a recent and modular setup based on &lt;a href="https://flake.parts" rel="noopener noreferrer"&gt;flake-parts&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I tried the second option a few years ago but have not tried the third one. I have been using the first one, using classic Nix. Only recently, I have refactored some of my projects to use &lt;a href="https://wiki.nixos.org/wiki/Flakes" rel="noopener noreferrer"&gt;Nix Flakes&lt;/a&gt;. Today, I also refactored my Haskell project &lt;a href="https://github.com/vst/haskell-template-hebele" rel="noopener noreferrer"&gt;template&lt;/a&gt; to use Flakes.&lt;/p&gt;

&lt;p&gt;My plan is to invest some time in &lt;em&gt;flake-parts&lt;/em&gt; and &lt;a href="https://horizon-haskell.net/" rel="noopener noreferrer"&gt;Horizon Haskell&lt;/a&gt;. In this post, I will simply go through my template's &lt;code&gt;flake.nix&lt;/code&gt; file and show what it does.&lt;/p&gt;

&lt;p&gt;The template may evolve, but the examples in this post reference a &lt;a href="https://github.com/vst/haskell-template-hebele/tree/5ed842cfe9538a9e3d935ac2fd0d039b3c37080c" rel="noopener noreferrer"&gt;pinned version&lt;/a&gt; for consistency. Also note that the template is a GitHub Repository Template that you can use to create a new repository with the same structure. Check out the &lt;code&gt;run-template.sh&lt;/code&gt; script that will help you update template variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flake.nix
&lt;/h2&gt;

&lt;p&gt;Here is the &lt;code&gt;flake.nix&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Haskell Project Template"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixos-24.11"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:numtide/flake-utils"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;eachDefaultSystem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt;
        &lt;span class="c"&gt;## Import nixpkgs:&lt;/span&gt;
        &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c"&gt;## Load readYAML helper:&lt;/span&gt;
        &lt;span class="nv"&gt;readYAML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;callPackage&lt;/span&gt; &lt;span class="sx"&gt;./nix/read-yaml.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c"&gt;## Read package information:&lt;/span&gt;
        &lt;span class="nv"&gt;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;readYAML&lt;/span&gt; &lt;span class="sx"&gt;./package.yaml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c"&gt;## Get our Haskell:&lt;/span&gt;
        &lt;span class="nv"&gt;thisHaskell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskellPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;overrides&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;super&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;callCabal2nix&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="sx"&gt;./.&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c"&gt;## Prepare dev-test-build script:&lt;/span&gt;
        &lt;span class="nv"&gt;dev-test-build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;writeShellApplication&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cabal-dev-test-build"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;builtins&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;readFile&lt;/span&gt; &lt;span class="sx"&gt;./nix/dev-test-build.sh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;runtimeInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;bash&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;bc&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;moreutils&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c"&gt;## Prepare Nix shell:&lt;/span&gt;
        &lt;span class="nv"&gt;thisShell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;shellFor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c"&gt;## Define packages for the shell:&lt;/span&gt;
          &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

          &lt;span class="c"&gt;## Enable Hoogle:&lt;/span&gt;
          &lt;span class="nv"&gt;withHoogle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="c"&gt;## Build inputs for development shell:&lt;/span&gt;
          &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c"&gt;## Haskell related build inputs:&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;apply-refact&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cabal-fmt&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cabal-install&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cabal2nix&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;fourmolu&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell-language-server&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hlint&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;hpack&lt;/span&gt;
            &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;weeder&lt;/span&gt;

            &lt;span class="c"&gt;## Our development scripts:&lt;/span&gt;
            &lt;span class="nv"&gt;dev-test-build&lt;/span&gt;

            &lt;span class="c"&gt;## Other build inputs for various development requirements:&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;docker-client&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;git&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nil&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nixpkgs-fmt&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nodePackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;prettier&lt;/span&gt;
            &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;upx&lt;/span&gt;
          &lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="nv"&gt;thisPackage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;justStaticExecutables&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;overrideAttrs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;oldAttrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;oldAttrs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="nv"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;git&lt;/span&gt;
              &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;makeWrapper&lt;/span&gt;
              &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ronn&lt;/span&gt;
            &lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="nv"&gt;postFixup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;oldAttrs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;postFixup&lt;/span&gt; &lt;span class="nv"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              ## Create output directories:&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              mkdir -p $out/{bin}&lt;/span&gt;&lt;span class="err"&gt;

&lt;/span&gt;&lt;span class="s2"&gt;              ## Wrap program to add PATHs to dependencies:&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;              wrapProgram $out/bin/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --prefix PATH : &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;makeBinPath&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nv"&gt;thisDocker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;dockerTools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;buildImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"now"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="nv"&gt;copyToRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;buildEnv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"image-root"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;cacert&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;pathsToLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"/etc"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;

          &lt;span class="nv"&gt;runAsRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            #!&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;runtimeShell&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;dockerTools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;shadowSetup&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            groupadd -r users&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            useradd -r -g users patron&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="nv"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"patron"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nv"&gt;Entrypoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;thisPackage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="nv"&gt;Cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="kn"&gt;in&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;## Project packages output:&lt;/span&gt;
        &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisPackage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;docker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisDocker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c"&gt;## Project development shell output:&lt;/span&gt;
        &lt;span class="nv"&gt;devShells&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisShell&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 125-line &lt;code&gt;flake.nix&lt;/code&gt; file might look a bit overwhelming at first, but it is actually quite simple considering that this file gives us the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A Nix-installable package,&lt;/li&gt;
&lt;li&gt; A Docker image with the package application as the entrypoint, and&lt;/li&gt;
&lt;li&gt; A development shell with all the development dependencies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are some placeholders in the file, such as building &lt;code&gt;man&lt;/code&gt; pages with &lt;code&gt;ronn&lt;/code&gt; during the package's &lt;code&gt;postFixup&lt;/code&gt; phase. I am not using this feature in all of my projects, but it comes in handy when I need it. Likewise, we can add extra &lt;code&gt;bin&lt;/code&gt; paths to the &lt;code&gt;PATH&lt;/code&gt; variable in the &lt;code&gt;postFixup&lt;/code&gt; phase for system binaries.&lt;/p&gt;

&lt;p&gt;Let us go over notable parts and features of the file.&lt;/p&gt;

&lt;p&gt;First of all, we do not refer to the name of the package. Instead, we load the &lt;code&gt;package.yaml&lt;/code&gt; file and read the name and version from it. Note that the &lt;code&gt;readYAML&lt;/code&gt; function is a simple helper I manage in my &lt;code&gt;nix&lt;/code&gt; directory as there is no built-in for reading YAML files in Nix yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;readYAML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;callPackage&lt;/span&gt; &lt;span class="sx"&gt;./nix/read-yaml.nix&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nv"&gt;package&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;readYAML&lt;/span&gt; &lt;span class="sx"&gt;./package.yaml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we create a Haskell package set with our package included:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;thisHaskell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskellPackages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;overrides&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;super&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;callCabal2nix&lt;/span&gt; &lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="sx"&gt;./.&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is used when we create the development shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;thisShell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;shellFor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nv"&gt;buildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c"&gt;## ...&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and when we create the package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;thisPackage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;haskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;justStaticExecutables&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;thisHaskell&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;overrideAttrs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;oldAttrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;oldAttrs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="nv"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c"&gt;## ...&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;postFixup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;oldAttrs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;postFixup&lt;/span&gt; &lt;span class="nv"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      ## ...&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;    ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we build a Docker image with the package as the entrypoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;thisDocker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;dockerTools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;buildImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;## ...&lt;/span&gt;
  &lt;span class="nv"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"patron"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;Entrypoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;thisPackage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bin/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;package&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;Cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tools I include in this &lt;code&gt;flake.nix&lt;/code&gt; file come in handy, especially with other features of this template. For example, I included a &lt;code&gt;cabal dev-test-build&lt;/code&gt; script that helps me build the package, run tests, linters and formatters. Additionally, and, probably most importantly, I have a script to statically build the application in a Docker image and compress it with &lt;code&gt;upx&lt;/code&gt; to reduce the size of the final binary.&lt;/p&gt;

&lt;p&gt;I am pretty sure that there are many better ways to do what I do in this template repository, but I am happy using it to quickly spin up a new Haskell project. There are quite a few other things and methods I use in real-life projects, such as multi-package builds and &lt;a href="https://github.com/googleapis/release-please-action" rel="noopener noreferrer"&gt;GitHub Release Please Action&lt;/a&gt; integration, which are quite easy to add upon this template.&lt;/p&gt;

&lt;p&gt;If you find this template useful, consider starring the &lt;a href="https://github.com/vst/haskell-template-hebele" rel="noopener noreferrer"&gt;repository&lt;/a&gt; or bookmarking it for future reference. You might also want to check out my new &lt;a href="https://github.com/vst/nix-flake-templates" rel="noopener noreferrer"&gt;Nix Flake Templates&lt;/a&gt; collection, where I plan to share more Haskell setups as my use cases expand.&lt;/p&gt;

</description>
      <category>haskell</category>
      <category>nix</category>
      <category>docker</category>
      <category>test</category>
    </item>
  </channel>
</rss>
