← writing
// post

A Stateless Revolution

Haskell · IaC · Cloud · Nix

The Unexpected Virtue of Idempotence

Introduction

In a bustling startup, a dusty laptop sat in the corner of the server room, perpetually plugged in, with a faded sticky note reading, ‘DO NOT UNPLUG.’ No one knew why, but the network crashed whenever it was powered off. The culprit? A cryptic block of code on that machine, managing state for a legacy system no one dared touch. This is the consequence of mismanaged state, one of Computer Science’s longest standing problems. Innovation in syntax, process, and tooling have continuously attempted to address this issue since the advent of this discipline, but none have come to a perfect conclusion.

If state management has such a long history, why am I discussing it now of all times then? In recent months I took up the hobby of learning Haskell. Haskell is quite different from most programming languages. It follows a completely functional paradigm, which I will explain in more depth later. While learning Haskell and its concepts, I started to notice parallels to concepts in the infrastructure space - particularly in regards to IaC (Infrastructure as Code). This particularly stood out to me because the types of teams managing infrastructure and studying Haskell rarely overlap. Infrastructure teams are often more reactive and heavily focused on real world application, whereas Haskell and functional programming are deeply tied to Academia and theory, with very specialized applications. These 2 specializations with very little overlap had tried to solve issues of state management with very similar goals, albeit with different implementations. Both groups strive to achieve idempotence, or consistent output for multiple executions, and implement some form of statelessness, where logic is separated from any external being.

Terraform and Declarative Configuration

On the infrastructure side of the spectrum lies Terraform. While Terraform is one of the most popular IaC tools and the one I will use here, these same concepts hold true for many others, such as AWS Cloudformation, Docker Compose Files, and Azure ARM templates. Terraform’s configuration is referred to as “declarative”. Declarative configuration “declares” the state desired at the end, rather than providing the steps on how to build it. This is idempotent by nature, and will always produce the same results given the same configuration. Think of it like going to a restaurant vs cooking a meal at home. You place an order and maybe have a couple small customization, but you’re not providing the chef with explicit instructions on how to make the dish. This has the added bonus that your Terraform files are stateless. Whether you are changing an existing resource or creating a new one, your Terraform file will look identical, because all of that logic is taken care of elsewhere. That’s not to say state isn’t important when using Terraform, state management is one of the most important aspects of implementing it correctly. However, that state is stored away in in a separate state file, and does not change your actual code. This is a practical and fairly simple solution, but what if this separation of state was solved in a more mathematical manner?

HashiCorp HCL
# Terraform: declarative & idempotent
# Declares an S3 bucket; applying twice produces the same bucket once.
resource "aws_s3_bucket" "app_bucket" {
  bucket = "my-app-bucket"     # name of the bucket
  acl    = "private"           # access policy
}

Functional Programming and Mathematical Accuracy

On the theoretical end of the spectrum lies Functional Programming, and in this example, Haskell. Explaining the core principles of functional programming is probably one of the most difficult aspects of this discussion, and can easily veer into irrelevant tangents. For this reason I will keep the explanation fairly surface level. In Haskell, all functions are pure. A pure function is a function that always produces the same output for the same input, and has no side effects. A side effect is any operation that occurs outside the scope of the input and output of a function. Thus, within Haskell, state cannot be modified unless explicitly stated within a function’s input and output, and those changes will be 100% consistent across every run. Thus, a pure function is idempotent given the same input, and it’s logic is stateless, as it is free of side-effects. Underpinning Haskell’s concepts are strong mathematical foundations, guaranteeing consistency.

Haskell
-- Sum even numbers; Nothing if list is empty
sumEvens :: [Int] -> Maybe Int
sumEvens xs = if null xs
                then Nothing
                else Just . sum $ filter even xs

Containers and Statelessness - More Idempotence in the Cloud

With some theory under out belt, lets touch tie it back to one of the cloud’s darling technologies: Containers. Containers are like a “mini-VM” that share the host’s kernel, only include the minimum software components needed, and most importantly, are stateless. This means that any change that occurs within a container are completely discarded when it is shut down, and an initial state is created from scratch every time it is powered on. The only way to have persistent data is to mount external storage volumes. This allows for large amounts of horizontal scaling using platforms like Kubernetes, and is a fundamental building block of cloud computing and modern infrastructure. Because containers are stateless, the code deployed in them must be idempotent, lest your application will produce unreliable results upon redeployment. In many regards a container could be viewed as a pure function, where its inputs are the contents of the external volumes mounted within, environment variables, and configurations, and its outputs are any changes, logs, etc. it produces.

YAML
# Docker Compose: stateless & idempotent by design
version: "3.8"
services:
  app:
    image: my-app:latest        # the container “image”
    restart: unless-stopped      # same behavior on restarts
    environment:
      - NODE_ENV=production      # input: environment variable
    volumes:
      - ./data:/app/data         # only this external folder persists
    ports:
      - "80:8080"                # map host port 80 to container 8080

Nix: Idempotent Configuration through Functional Programming

While all of these connections are nice on paper, there remains an elephant in the room: most of these IaC tools are written in Go, not a functional language like Haskell. If these ideas are truly compatible, shouldn’t it be the other way around? In many regards this is a product of popularity, accessibility, and self-selection. After all, Haskell is known for inspiring people to write Substack articles more than producing production code. However, there is one tool seeing a rise in popularity that fits all of these pieces together. NixOS is a unique Linux Distribution that allows you to configure your entire operating system within a single file. That configuration is declarative, often looking quite similar to Terraform. All NixOS modules are written in its own Nix programming language - a language that is functional. Nix’s popularity is quite young, and limited to enthusiasts and bleeding edge shops. However, I believe its a sign of things to come. While the Nix project was born many moons ago, its ideas align far more with modern systems, where reproducability and consistency are paramount.

Nix
# NixOS: pure‑functional & declarative system config
# Describes exactly what to install and which services to run.
{ pkgs, ... }:
{
  # Install these packages every time
  environment.systemPackages = [
    pkgs.neovim
    pkgs.nginx
  ];

  # Ensure nginx is enabled and running
  services.nginx.enable = true;
  system.autoUpgrade.enable = true;
}
# nixos-rebuild switch → applies once, then reports “no changes” on repeat

Conclusion

Managing state has proven to be a fundamental problem for teams across the computer science field. Functional programmers strictly limit their interactions with state, ensuring all changes are explicit. They solve this problem through their programming language, with a mathematical approach. Tools like Terraform and Containers separate state from the code entirely, storing it in separate configurations away from code. Their solution is a part of a tool, separating files and process steps so users never have to interact with state to begin with. Nix combines these ideas, achieving declarative configuration through its own functional language. Ultimately, many of these solutions work very differently under the hood, but have identified the same problem. State should be stored separately from logic, and interacted with very explicitly. Perhaps different teams can learn from eachother, improving their grasp of the problem, or perhaps this state of affairs is idempotent, and will produce the same disconnect unless we input a different organizational structure.

Originally published on Substack.