Below you will find pages that utilize the taxonomy term “Jsl”
Rerum: Pattern Matching and Term Rewriting in Python
December 16, 2025
Rerum (Rewriting Expressions via Rules Using Morphisms) is a Python library for pattern matching and term rewriting. It makes symbolic computation accessible through a readable DSL while keeping a clean separation between trusted and untrusted code.
The Problem
Traditional symbolic math systems tend toward two extremes. Monolithic systems like Mathematica bundle everything in. Lighter tools force you to write complex recursive traversals every time you want to transform an expression. I wanted something in between: a simple, extensible system where transformation rules are data that can be loaded, combined, and inspected.
The SICP Connection
This design reflects a core idea from Structure and Interpretation of Computer Programs: when a problem domain is complex enough, the right move is to build a language for it. Rerum’s rule DSL makes transformation logic inspectable, composable, and safe.
The engine composition operators (>> for sequencing, | for union) ensure closure: combining engines yields an engine. Same principle that makes Scheme’s procedures powerful. You can pass them, return them, combine them, no special cases. Transformation strategies are first-class.
A Readable DSL
At the heart of rerum is a domain-specific language for defining rewrite rules:
# Algebraic simplification
@add-zero[100] "x + 0 = x": (+ ?x 0) => :x
@mul-one[100]: (* ?x 1) => :x
@mul-zero[100]: (* ?x 0) => 0
Each rule has:
- A name:
@add-zerofor debugging and tracing - Optional priority:
[100]determines firing order when multiple rules match - Optional description: Human-readable explanation
- A pattern:
(+ ?x 0)matches addition with zero - A skeleton:
:xis the replacement
The pattern syntax:
| Syntax | Meaning |
|---|---|
?x | Match anything, bind to x |
?x:const | Match only numbers |
?x:var | Match only symbols |
?x:free(v) | Match expressions not containing v |
?x... | Variadic, capture remaining arguments |
Symbolic Differentiation in 15 Lines
Here’s a calculus ruleset that computes symbolic derivatives:
[basic-derivatives]
@dd-const[100]: (dd ?c:const ?v:var) => 0
@dd-var-same[100]: (dd ?x:var ?x) => 1
@dd-var-diff[90]: (dd ?y:var ?x:var) => 0
[rules]
@dd-sum: (dd (+ ?f ?g) ?v:var) => (+ (dd :f :v) (dd :g :v))
@dd-product: (dd (* ?f ?g) ?v:var) => (+ (* (dd :f :v) :g) (* :f (dd :g :v)))
@dd-power: (dd (^ ?f ?n:const) ?v:var) => (* :n (* (^ :f (- :n 1)) (dd :f :v)))
@dd-exp: (dd (exp ?f) ?v:var) => (* (exp :f) (dd :f :v))
@dd-log: (dd (ln ?f) ?v:var) => (/ (dd :f :v) :f)
@dd-sin: (dd (sin ?f) ?v:var) => (* (cos :f) (dd :f :v))
@dd-cos: (dd (cos ?f) ?v:var) => (* (- (sin :f)) (dd :f :v))
With these rules loaded:
from rerum import RuleEngine, E
engine = RuleEngine.from_file("calculus.rules")
# d/dx(x^2) = 2x
engine(E("(dd (^ x 2) x)")) # => (* 2 (* (^ x 1) 1))
The result needs simplification (another ruleset), but the differentiation itself is purely declarative.
The Security Model: Rules vs. Preludes
A key architectural decision: the separation between rules (untrusted, serializable) and preludes (trusted Python code). Rules define structural transformations. They can reference operations via the (! op args...) compute form, but those operations must be explicitly provided by the host.
JSL: A Functional Language Where Code Is JSON
November 20, 2024
JSL (JSON Serializable Language) is a functional programming language where code is JSON. The whole point: if your code is already valid JSON, serialization stops being a problem you solve and starts being a property you have.
The Problem
Most languages treat serialization as an afterthought. You write code in one representation, data lives in another, and moving computation across a network requires marshalling, pickling, or worse.
The traditional approach:
# Code: Python AST, bytecode, machine code
def factorial(n):
return 1 if n <= 1 else n * factorial(n - 1)
# Data: JSON
data = {"n": 5}
# Problem: Can't serialize the function, can't send it over network
JSL’s approach:
["do",
["def", "factorial",
["lambda", ["n"],
["if", ["<=", "n", 1],
1,
["*", "n", ["factorial", ["-", "n", 1]]]]]],
["factorial", 5]]
That program is valid JSON. Any JSON parser reads it. Any HTTP endpoint transmits it. Any database stores it. Any program generates it. Code and data are the same thing, which is Lisp’s oldest idea wearing a new coat.
Design Principles
JSON as Code and Data
All JSL programs and data structures are representable as standard JSON. This means universal parsing, generation, and compatibility with every tool that speaks JSON (which is basically every tool).
Serializable Closures
This is the thing I actually care about. Closures (functions with captured environment) are fully serializable:
from jsl import JSLRunner
runner = JSLRunner()
# Create a closure that captures 'multiplier'
runner.execute('''
(do
(def multiplier 10)
(def make-multiplier (lambda (x) (* x multiplier)))
(def my-func (make-multiplier 5)))
''')
# Serialize the closure
serialized = runner.serialize_value(runner.env.get('my-func'))
# Send over network, store in database, etc.
# Later, deserialize and execute
deserialized_func = runner.deserialize_value(serialized)
result = runner.apply(deserialized_func, [3]) # 30
The closure retains its captured multiplier variable even after serialization. In Python you’d reach for pickle, which is unsafe and fragile. Here it just works because the closure was JSON the whole time.
Effect Reification
Side effects are not executed directly. They’re described as data structures:
; This doesn't perform I/O directly
(host file-read "/tmp/data.json")
; Instead, it produces a data structure:
{
"effect": "host",
"command": "file-read",
"args": ["/tmp/data.json"]
}
The host environment controls, audits, or modifies these effects before execution. This is basically the algebraic effects pattern: pure computation produces descriptions of what it wants done, and the runtime decides whether to actually do it.