DEV Community

Vehbi Sinan Tunalioglu
Vehbi Sinan Tunalioglu

Posted on • Originally published at thenegation.com

Abusing Haskell: Executable Blog Posts

Why? Because I can, and it is a rainy Sunday.

I post my notes on my blog, Hashnode and dev.to, which require slightly different markdown formats. I have been doing the sane thing to fix formats so far. But it is a rainy Sunday and I am bored, so I decided to make this blog post an executable Haskell program to do the same.

The Non-Problem

I am blogging on https://thenegation.com. It is a static site generated by Zola in a Nix shell. I write my blog posts in Markdown.

After I publish a post on my blog, I cross-post it to my dev.to and Hashnode blogs with the hope of reaching a wider audience. This is a manual process. One of the steps in this process is to copy the post content in Markdown format that is compatible with the target platform.

The Friendly Solution

I do not care much about the specifics of the original and target Markdown formats. Following assumptions worked so far: The original format uses newlines as soft-wraps, and the target format may use them as hard-wraps. Also, the output format does not need a front-matter.

So, following was sufficient and convenient:

pandoc \
  --from markdown \
  --to markdown \
  --wrap=none \
  --strip-comments=true \
  content/posts/2024-08-04_abuse-haskell.md
Enter fullscreen mode Exit fullscreen mode

What is wrong with this solution? It is too boring to my liking.

Alternative Solutions

There are a few alternatives to this solution:

  1. Stop blogging
  2. Do not cross-post
  3. Use standard Markdown everywhere
  4. Write a Visual Basic script to do the conversion

Clearly, none of these solutions would offend anyone enough to be interesting. So, pass!

The Ultimate Solution

What if I would write a blog post that would be an executable that I can use to convert my blog posts to the target format?

This is a terrible idea. So, let’s do it!

The Plan

This very blog post is going to be a literate Haskell program that I can compile with ghc. Then, I will use the compiled executable to convert the format of my Markdown files removing soft line-wraps, front-matter and HTML comments.

Since I am using a Nix shell for my blog’s codebase, I will provision a GHC inside it:

{
  ##...

  ghc = pkgs.haskellPackages.ghcWithPackages (hpkgs: [
    hpkgs.markdown-unlit
    hpkgs.pandoc
  ]);

  thisShell = pkgs.mkShell {
    buildInputs = [
      ## ...

      ghc

      ## ...
    ];

    NIX_GHC = "${ghc}/bin/ghc";
    NIX_GHCPKG = "${ghc}/bin/ghc-pkg";
    NIX_GHC_DOCDIR = "${ghc}/share/doc/ghc/html";
    NIX_GHC_LIBDIR = "${ghc}/lib/ghc-9.6.5/lib";
  };

  # ...
}
Enter fullscreen mode Exit fullscreen mode

I am so ready for the implementation…

The Implementation

Let’s start with adding some MSG for tastier Haskell, also known as GHC Language Extensions:

{-# LANGUAGE OverloadedStrings #-}
Enter fullscreen mode Exit fullscreen mode

We need some imports:

import System.Environment (getArgs)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Text.Pandoc as P
Enter fullscreen mode Exit fullscreen mode

Our workhorse function is unfortunately quite simple:

convert :: T.Text -> IO T.Text
convert txt = P.runIOorExplode $ do
  md <- P.readMarkdown readerOptions txt
  P.writeMarkdown writerOptions md
  where
    readerOptions = P.def
        { P.readerExtensions = P.enableExtension P.Ext_yaml_metadata_block $ P.getDefaultExtensions "markdown"
        , P.readerStripComments = True
        }
    writerOptions = P.def
        { P.writerExtensions = P.githubMarkdownExtensions
        , P.writerWrapText = P.WrapNone
        }
Enter fullscreen mode Exit fullscreen mode

Now, we can implement our entrypoint function:

main :: IO ()
main = do
  path <- head <$> getArgs
  iTxt <- TIO.readFile path
  oTxt <- convert iTxt
  TIO.putStrLn oTxt
Enter fullscreen mode Exit fullscreen mode

We are done with the program. We can run our blog post via runhaskell on our blog post. But first, we need to symlink our Markdown file (.md) with a literate Haskell file extension (.lhs) so that GHC is not upset:

ln -sr \
  content/posts/2024-08-04_abuse-haskell.md \
  content/posts/2024-08-04_abuse-haskell.lhs
Enter fullscreen mode Exit fullscreen mode

Then, we can run the blog post on the blog post itself:

runhaskell \
  -pgmLmarkdown-unlit \
  content/posts/2024-08-04_abuse-haskell.lhs \
  content/posts/2024-08-04_abuse-haskell.md
Enter fullscreen mode Exit fullscreen mode

We can even compile our blog post into an executable and use it later:

$ ghc -pgmLmarkdown-unlit content/posts/2024-08-04_abuse-haskell.lhs
[1 of 2] Compiling Main             ( content/posts/2024-08-04_abuse-haskell.lhs, content/posts/2024-08-04_abuse-haskell.o )
[2 of 2] Linking content/posts/2024-08-04_abuse-haskell
$ content/posts/2024-08-04_abuse-haskell content/posts/2024-08-04_abuse-haskell.md
Enter fullscreen mode Exit fullscreen mode

I even added a script in my Nix shell that aliases above so that I can use it all my life:

dev-md-format content/posts/2024-08-04_abuse-haskell.md
Enter fullscreen mode Exit fullscreen mode

Wrap-Up

This solution will help me continue abusing Haskell in the future. It was stupid enough, as well, to deserve its own GitHub template repository:

https://github.com/vst/literate-haskell-nix-example

You can play with it, or see it in action in my blog’s source code.

Joke aside; rain is over.

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay