Show HN: A Lisp where each function call runs a Docker container

2026-02-194:198329github.com

A Docker image is a piece of executable code that produces some output given some input. - a11ce/docker-lisp

A Docker image is a piece of executable code that produces some output given some input.

Screenshot of a program trace, docker events, and docker stats..

First, build the base images and builtins.

./scripts/build-base
./scripts/build-builtins

Then, run the tests. Be patient.

Run eval to evaluate expressions.

./scripts/run eval "(cons 1 2)"

Pass --trace to run to see all calls.

./scripts/run --trace eval "(car (cdr (cons 1 (cons 2 (list)))))"

You can also use docker stats and docker events to watch evaluation.

You can write programs:

FROM docker-lisp/eval
CMD ["(define fact (lambda (n) (if (number-equals n 0) 1 (multiply n (fact (subtract n 1))))))", "(fact 3)"]

Build them with

./scripts/build <program path> [name]

or

./scripts/build <program path> # Uses the filename if no name is specified

Then run with

Script Purpose
build <file> [name] Build a Dockerfile into docker-lisp/<name>. Defaults to basename.
build-base Build base images (docker-lisp/base-racket, docker-lisp/base-call)
build-builtins Build all builtin images
run [--trace] [--no-cleanup] <image> [args] Run a docker-lisp/<image> container
run-tests [--no-trace] [--rebuild-base] [prefix filter] Run the tests (with traces by default)
clean Kill all docker-lisp/* containers and remove all built docker-lisp/* images

Read the original article

Comments

  • By codethief 2026-02-1911:492 reply

    People here are laughing of course but I do think there is a deeper truth behind this that's worth exploring:

    > A Docker image is a piece of executable code that produces some output given some input.

    The ideas behind containerization and sandboxing are rather closely related to functional programming and controlling side effects. If binaries always only read stdin and wrote to stdout, we wouldn't need sandboxes – they would be pure functions.

    In the real world, though, binaries usually have side effects and I really wish we could control those in a more fine-grained manner. Ideally, binaries couldn't just do anything by default but actually had to declare all their side effects (i.e. accessing env variables, config, state, cache, logs, DBUS/Xserver/Wayland sockets, user data, shared libraries, system state, …), so that I could easily put them in a sandbox that's tailored to them.

    Conversely, I'm waiting for the day when algebraic effects are so common in programming languages that I can safely execute an untrusted JavaScript function because I have tight control over what side effects it can trigger.

    • By juffasdfwa 2026-02-1915:08

      I too really want algebraic effects but the majority are panicans and exceptionauts. PL's will always cater to majority. We still don't have proper tail calls because the luddites want stack metaphors. Javanese & Gophers can't conceive of a world of pattern matching, sum types, and null/nil-safe code. Now we have machines writing code for us. It's over.

    • By chr15m 2026-02-1923:061 reply

      I'm sure that would work just like how phone apps asking for permission to do each thing has resulted in no phone user ever getting pwned and doxxed by their apps - phone apps are completely safe now, yay!

      • By codethief 2026-02-1923:411 reply

        I think we're talking about fundamentally different things. :) You're talking about the UX of granting permissions, I'm talking about how permissions get implemented at the technical level, irrespective of how you arrived at them.

        Surely your proposed solution is not "Don't implement a permission system to begin with"?

        • By chr15m 2026-02-2014:32

          Sorry about the sarcasm in my reply.

          I guess what I am saying is at the end of the day you need the program to do the thing. Whatever mutation it needs to do to accomplish the task, that's what you're going to allow. That's exactly what happens with phone app permissions. Everybody just lets Facebook use their microphone (not me of course, but most people).

          What you describe would be super cool though. If every program let you know ahead of time what it was going to try to read and write in the world. That does indeed sound useful!

  • By lioeters 2026-02-197:18

    The best kind of absurd experiment, pushing the limits of technology and reason, just to see if it can be done. The adventurous spirit of "What if?" and "Why not!" I love when such an idea is implemented seriously, like having a CI action to test a factorial function. I shudder at its monstrous beauty.

    Is there a spark of practical potential? It's intriguing to imagine, how a Docker-like container could be a language primitive, as easy to spin up like a new thread or worker. Not sure what advantage that'd bring, or any possible use case. It reminds me of..

      2.1 Xappings, Xets, and Xectors
    
      All parallelism in Connection Machine Lisp is organized around a data structure known as the zapping (pronounced “zapping,” and derived from “mapping”). Xappings are data objects similar in structure to arrays or hash tables, but they have one essential characteristic: operations on the entries of xappings may be performed in parallel.
    
    Thinking Machines Technical Report PL87-6. Connection Machine Lisp: A Dialect of Common Lisp for Data Parallel Programming. https://archive.org/details/tmc-technical-report-pl-87-6-con...

  • By teaearlgraycold 2026-02-194:222 reply

    I'm impressed GitHub managed to handle this beast:

    https://github.com/a11ce/docker-lisp/actions/runs/2216831271...

    500+ container invocations to compute factorial(3)

    • By ConceptJunkie 2026-03-0317:52

      Why, I bet it could factor 15 or 21 better than the latest quantum computers!

    • By wolfi1 2026-02-199:441 reply

      I don't wanna know how much containers it spins up for fibonacci(3)

HackerNews