
Security News
Software Engineering Daily Podcast: Feross on AI, Open Source, and Supply Chain Risk
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.
spork-lang
Advanced tools
Spork is a Lisp that runs on Python. It gives you the expressive power of macros, immutable data structures, and a modern REPL driven development experience.
Spork compiles to Python AST and can be imported directly via a standard import hook. This gives you seamless interoperability with existing Python libraries and tools. No wrappers (unless you want a lispy one) or FFI layers are needed, just continue using your favorite Python libraries.
Spork adds features that Python natively lacks, such as:
Spork is currently in alpha. The language, standard library, and tooling are all under active development. Breaking changes may occur between releases. We welcome feedback, issues, and contributions!
To avoid any confusions, here's what Spork is not:
Spork is built on a few core opinions:
[1 2 3] isn't a Python list; it is a persistent vector. Your data is immutable by default, ensuring that state management remains predictable as complexity grows.cargo or go. It handles the bridge between Spork source and the Python environment so you don't have to configure build hooks manually.It's a philosophy that tries to balance expressiveness and restraint, plenty of room to create, while minimizing room to trip.
The recommended way to install Spork is via the install.sh script or pipx. Both options isolate the tool environment while making the CLI globally available.
Prerequisites: Python 3.10+ and pip installed.
Using the install.sh script (Linux/MacOS/WSL):
Recommended for most users as it doesn't rely on anything but Python. Spork will be installed to ~/.local/bin/spork and a virtual environment will be created at ~/.spork/venv. Upgrading is as simple as re-running the script.
$ curl https://raw.githubusercontent.com/spork-it/spork-lang/refs/heads/main/install.sh | sh
Using Pipx (Linux/MacOS/Windows/WSL):
Recommended if you already use pipx to manage your Python CLI tools.
$ pipx install spork-lang
Continue to the Quick Start section for details.
Using Pip (Linux/MacOS/Windows/WSL):
Recommended when you are embedding Spork in an existing Python project.
$ pip install spork-lang
Continue to the Using Spork in an existing Python project section for details.
If you wish to contribute to Spork or modify the compiler:
You'll first need to clone the repository and set up the virtual environment.
$ git clone https://github.com/spork-it/spork-lang.git
$ cd spork-lang
# Sets up virtual environment and builds C extensions
$ make venv
# Run the test suite
$ make test
# Enter the Spork REPL using the development environment
$ bin/spork
Once installed, simply run spork to enter the Read-Eval-Print Loop.
$ spork
Spork REPL - A Lisp for Python
user> (+ 1 2 3)
6
user> (map inc [1 2 3])
[2 3 4]
Create a file named hello.spork:
;; hello.spork
(defn greet [name]
(print (fmt "Hello, {}!" name)))
(greet "Spork")
Run it with:
$ spork hello.spork
Hello, Spork!
Spork provides Persistent Data Structures (PDS) implemented in C for performance. These are the default literals in the language.
;; Vectors
(def v [1 2 3])
(def v2 (conj v 4))
(print v) ; [1 2 3] - original is unchanged
(print v2) ; [1 2 3 4] - new structure sharing memory with old
;; Maps
(def m {:name "Spork" :version 1})
(def m2 (assoc m :version 2))
(print m) ; {:name "Spork", :version 1}
(print m2) ; {:name "Spork", :version 2}
;; Sets
(def s #{1 2 3})
(contains? s 2) ; true
; create new subset of s without 2
(def s2 (disj s 2))
(print s) ; #{1 2 3}
(print s2) ; #{1 3}
Spork compiles to Python, so interop is seamless.
;; Imports
(ns examples
(:import [os] [random] [antigravity]) ; Host (Python) Imports
(:require [std.json :as j]) ; Spork Imports
;; Method calls (dot syntax)
(def text "hello world")
(.upper text) ; "HELLO WORLD"
;; Attribute access
(print os.name) ; e.g. "posix" or "nt"
;; Mixing Python types (escape hatch)
(def py-list (list [1 2 3])) ; Convert Spork Vector to Python list
(.append py-list 4) ; Mutate it in place
(py-list.append 5) ; alternative call syntax
(print py-list) ; [1, 2, 3, 4, 5]
(def data {:name "Spork" :version 1.0}) ; Immutable Spork Map
(print (j.dumps data)) ; '{"name": "Spork", "version": 1.0}'
Python objects and Spork objects interoperate freely, no wrappers or FFI layers. thanks to structural sharing
Spork has first-class support for Python's async/await ecosystem.
;; a simple async function to fetch JSON data
(defn ^async fetch-data [^str url]
(async-with [session (aiohttp.ClientSession)]
(async-with [resp (.get session url)]
(await (.json resp)))))
Spork includes structural pattern matching out of the box. With match, you can destructure and branch on data shapes concisely.
(defn describe [x]
(match x
0 "zero"
(^int n) (+ "integer: " (str n))
[a b] (+ "vector pair: " (str a) ", " (str b))
{:keys [name]} (+ "Hello " name)
_ "something else"))
Spork supports Python type hints using metadata syntax. These compile down to standard Python type annotations.
(defn ^int add [^int x ^int y]
(+ x y))
Compiles to:
def add(x: int, y: int) -> int:
return x + y
This also applies to decorators in Python like @staticmethod or @classmethod.
(defclass MyClass []
(defn ^staticmethod static-method [^str msg]
(print msg)))
(MyClass.static-method "Hello from static method")
As a Lisp, Spork allows you to extend the compiler via macros.
(defmacro unless [test & body]
`(if ~test
nil
(do ~@body)))
(unless (= (add 1 1) 3)
(print "Math still works"))
Below is a complete Spork program that demonstrates macros, Python interop, pattern matching, type annotations, and persistent data structures while fetching data from the GitHub API. GitHub Stars Project
(ns stars.core
(:import
[requests]
[time :as t]))
;; Simple profiling macro using Python's time.perf_counter
(defmacro profile [label & body]
`(let [start# (t.perf_counter)
result# (do ~@body)
end# (t.perf_counter)
elapsed# (- end# start#)]
(print (+ ~label " took " (str elapsed#) "s"))
result#))
;; Fetch the star count for a given GitHub repo full name
(defn ^int fetch-stars [^str full-name]
(let [resp (requests.get (+ "https://api.github.com/repos/" full-name))]
(match resp.status_code
200 (get (resp.json) "stargazers_count")
404 0 ; missing repo → 0 stars
_ (throw (RuntimeError "GitHub API error")))))
;; Get top repos by star count
(defn top-repos [names]
;; Returns a SortedVector of Maps with :name and :stars
[sorted-for [full-name names]
{:name full-name
:stars (fetch-stars full-name)}
:key :stars :reverse true])
(defn main []
(let [repos ["pallets/flask"
"django/django"
"tiangolo/fastapi"
"psf/requests"]
ranked (profile "GitHub fetch" (top-repos repos))]
(for [row ranked]
(let [{:keys [name stars]} row]
(print stars "-" name)))))
(main)
;; Example Output:
; GitHub fetch took 0.1801389280008152s
; 92823 - tiangolo/fastapi
; 86079 - django/django
; 70890 - pallets/flask
; 53551 - psf/requests
Spork provides source-mapped error reporting, meaning that runtime errors point to the original .spork source files with accurate line numbers and code context—not the generated Python code.
Given this Spork file:
;; math.spork
(defn divide [a b]
(/ a b))
(defn nested-call [x]
(let [y (divide x 0)]
(+ y 10)))
(defn deep-stack []
(nested-call 42))
(deep-stack)
Running it produces a traceback that references the original Spork source:
Error: division by zero
Traceback (most recent call last):
File "math.spork", line 12, in <module>
(deep-stack)
~~~~~^~~~~~~
File "math.spork", line 10, in deep_stack
(nested-call 42))
^^^^^^^^^^^^^^^^
File "math.spork", line 6, in nested_call
(let [y (divide x 0)]
^^^^^^^^^^^^
File "math.spork", line 3, in divide
(/ a b))
^^^^^^^
ZeroDivisionError: division by zero
For standalone Spork projects, spork.it files provide a unified manifest similar to cargo.toml or package.json. Saving you from managing virtual environments, dependencies, and build scripts manually.
Note: If you are just adding Spork files to an existing Python application, you don't need a spork.it file. See Using Spork in an existing Python project for details.
Spork includes a scaffolding tool to set up a standard project structure with dependency management.
$ spork new my-project
✓ Created new Spork project: .../my-project
Next steps:
cd my-project
spork run # Run the project entrypoint
spork repl # Start the REPL in the project context
$ cd my-project/
$ tree
.
├── README.md
├── spork.it
└── src
└── my-project
└── core.spork
3 directories, 3 files
$ spork run
Project venv not found, initializing...
Creating virtual environment at .../my-project/.venv...
✓ Created virtual environment
✓ Upgraded pip
✓ Installed spork-lang (copied from current environment)
✓ All dependencies installed
Welcome to my-project!
spork.it)This generates a spork.it configuration file (the Spork equivalent of pyproject.toml), a source directory, and a test directory.
Spork aims to unify the fragmented Python tooling ecosystem. A project is defined by a spork.it file:
{:name "my-project"
:version "0.1.0"
:dependencies ["requests" "numpy>=1.20"]
:source-paths ["src"]
:test-paths ["tests"]
:main "my-project.core:main"}
Commands:
spork sync: Creates a virtual environment and installs dependencies defined in spork.it.spork run: Runs the project's main function.spork repl: Starts a REPL with the project's source roots and dependencies loaded.spork build: Compiles Spork source files to Python .py files in a .spork-out/ directory.spork dist: Builds a distributable package (wheel & archives) for the project.$ pip install spork-lang
spork once at startup to register the import hooks:# e.g. in your app's __init__.py or main.py
import spork
This uses a standard
importlibhook to allow importing.sporkfiles as if they were Python modules.
;; my_module.spork
(defn add [x y]
(+ x y))
from my_module import add
print(add(1, 2)) # 3
If you forget step 2 (
import spork), Python will just sayModuleNotFoundError: No module named 'my_module'because the .spork import hook hasn’t been installed yet.
You can use Spork's persistent data structures directly in Python by importing them from the spork.runtime.pds module. This gives Python developers access to the same immutable collections used in Spork.
from spork.runtime.pds import Vector, vec
v: Vector = vec([1, 2, 3])
v2 = v.conj(4)
print(v) # Vector([1, 2, 3])
print(v2) # Vector([1, 2, 3, 4])
Spork is a Lisp because we believe in Homoiconicity: the code is represented by the language's own data structures.
match or unless examples above) without waiting for the compiler developers to implement them.if, let, and do blocks all return values, reducing the need for temporary variables and side effects.Lisp's superpower is treating code as data, which means Spork can grow with you, not just run what you write. It's like having a language that politely asks: "Would you like to customize the universe today?"
Spork is not just a syntax skin; it is a runtime system optimized for the Python memory model.
Spork prioritizes safety over raw mutation speed.
Comparison of Spork Persistent Vector vs Native Python List for common operations:
| Operation (N=100k) | Python (Native) | Spork (PDS) | Difference |
|---|---|---|---|
| Vector random read (10k reads) | 0.46 ms | 1.44 ms | ~3x slower |
Build collection from range(N) | 2.84 ms | 5.38 ms | ~2x slower |
| Copy & update one list/vec element | 134.59 µs | 0.79 µs | ~170x faster |
| Copy & update one map entry | 1.01 ms | 1.07 µs | ~940x faster |
| Copy & update one set element | 416.82 µs | 0.90 µs | ~460x faster |
Benchmarks run on: AMD Ryzen 7 6800H, Linux 6.12, CPython 3.13.5
See Benchmarks for full details and methodology.
Note: Spork includes specialized
IntVectorandDoubleVectortypes. These support the Buffer Protocol but need further testing and benchmarking to verify zero-copy interop performance (promising early results).
spork.it).Spork ships with early Neovim and Emacs modes located in the repository (editors/ directory). These provide basic syntax highlighting and are useful for experimentation, but are not yet feature-complete. We recommend Parinfer or similar structural editing tools, these are not a replacement for them.
We have plans to author and maintain the core editor modes/plugins as first-class projects in the Spork ecosystem because we believe that editor support is essential for a great developer experience.
C-c C-j or M-x spork-jack-in RETC-c C-b, C-c C-r, and C-c C-c respectively.
C-c C-d on a symbol and C-c i for the inspector (currently basic).rainbow-delimiters-mode incorrectly highlighting on closing ] with spork-mode.parinfer-rust-mode seems to work well with spork-mode for structural editing.Emacs spork-mode is currently more feature complete than the LSP mode, but both are under active development.
These modes are evolving quickly and will improve over time. Contributions are welcome!
spork CLI with testing, linting, and formatting commandsCheck out the docs folder for more detailed documentation on language features, the standard library, and benchmarks of the persistent data structures.
FAQs
A Lisp to Python transpiler with persistent data structures
We found that spork-lang demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.

Security News
GitHub has revoked npm classic tokens for publishing; maintainers must migrate, but OpenJS warns OIDC trusted publishing still has risky gaps for critical projects.

Security News
Rust’s crates.io team is advancing an RFC to add a Security tab that surfaces RustSec vulnerability and unsoundness advisories directly on crate pages.