JSL (JSON Serializable Language) is a network-native functional programming language where code is JSON. Unlike traditional languages that treat serialization as an afterthought, JSL makes wire-format compatibility a first-class design principle, enabling truly mobile code with serializable closures and resumable distributed computation.
The Core Insight: Code as Data, Data as Code
Most programming languages face a fundamental problem: code and data live in different worlds.
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]]
In JSL, code IS JSON. The program above is valid JSON that can be:
- ✅ Parsed by any JSON parser
- ✅ Transmitted over HTTP
- ✅ Stored in databases
- ✅ Generated programmatically
- ✅ Inspected and transformed as data
Design Principles
1. JSON as Code and Data
All JSL programs and data structures are representable as standard JSON. This ensures universal parsing, generation, and compatibility with the entire JSON ecosystem.
2. Network-Native
The language is designed for seamless transmission over networks. No complex marshalling/unmarshalling beyond standard JSON processing.
3. Serializable Closures
JSL 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!
4. Effect Reification
Side effects are not executed directly but are 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.
5. Deterministic Evaluation
The core JSL evaluation (excluding host interactions) is deterministic, facilitating testing, debugging, and predictable behavior in distributed systems.
Dual Evaluation Engines
JSL provides two complementary evaluation strategies:
Recursive Evaluator — Traditional Interpreter
from jsl.evaluator import Evaluator
evaluator = Evaluator()
result = evaluator.eval(["*", ["+", 2, 3], 4]) # 20
Characteristics:
- Tree-walking interpreter
- Direct AST evaluation
- Natural implementation of language semantics
- Excellent for development and debugging
- Direct
Closureobject representation
Stack-Based Evaluator — Resumable Execution
from jsl.stack_evaluator import StackEvaluator
evaluator = StackEvaluator()
# Compile to JPN (JSL Postfix Notation) bytecode
jpn = compile_to_postfix(["*", ["+", 2, 3], 4])
# Result: [2, 3, "+", 4, "*"]
# Execute with step limits
result, state = evaluator.eval_partial(jpn, max_steps=100)
if state: # Computation paused
# State is fully serializable
serialized = state.to_dict()
# Resume later, possibly on different machine
final_result = evaluator.resume(serialized)
Characteristics:
- Compiles to postfix bytecode (JPN)
- Linear execution (no tree traversal)
- Natural step/gas metering
- Resumable computation across network boundaries
- Dict-based closure representation for JSON serialization
JPN: JSL Postfix Notation
JPN is JSL’s compiled bytecode format, designed for efficient stack-based evaluation:
Compilation
from jsl.compiler import compile_to_postfix, decompile_from_postfix
# S-expression
expr = ["if", ["=", "x", 5], "yes", "no"]
# Compile to JPN
jpn = compile_to_postfix(expr)
# JPN uses opcodes for control flow
# [var_lookup("x"), 5, "=", "if_start", "yes", "if_branch", "no", "if_end"]
# Can decompile back
original = decompile_from_postfix(jpn)
Benefits
- Linear Execution: Sequential instruction processing
- Natural Resumption: Stack state captures exact execution point
- Efficient Serialization: Flat array vs nested trees
- Cache-Friendly: Sequential memory access
- Simple VM: Minimal interpreter complexity
Resumable Computation
# Execute with step limit
result, state = evaluator.eval_partial(jpn, max_steps=1000)
if state: # Paused at step 1000
# Serialize entire execution state
checkpoint = {
"program": jpn,
"pc": state.pc, # Program counter
"stack": state.stack, # Operand stack
"env": state.env.to_dict(), # Environment
"call_stack": state.call_stack
}
# Send checkpoint to another machine
# Resume there
new_evaluator = StackEvaluator()
final = new_evaluator.eval_partial(jpn, state=State.from_dict(checkpoint))
This enables:
- Fault tolerance: Checkpoint and restart
- Load balancing: Move computation to less-busy nodes
- Edge computing: Pause on edge, resume in cloud
- Fair scheduling: Time-slice multiple programs
Syntax Options
JSL offers two syntactic representations:
JSON Array Syntax (Network-Native)
["do",
["def", "square", ["lambda", ["x"], ["*", "x", "x"]]],
["map", ["square"], ["@", [1, 2, 3, 4, 5]]]]
Advantages:
- Valid JSON (parseable anywhere)
- No special syntax to learn
- Easy to generate programmatically
- Network-transmissible
Lisp-Style Syntax (Human-Friendly)
(do
(def square (lambda (x) (* x x)))
(map square (@ [1 2 3 4 5])))
Advantages:
- Familiar to Lisp programmers
- More readable for humans
- Easier to write by hand
Both compile to the same internal representation.
Query and Transform Operations
JSL provides powerful declarative operations for data manipulation:
Where — Filtering
; Define data
(def users [@
{"name": "Alice", "age": 30, "role": "admin"}
{"name": "Bob", "age": 25, "role": "user"}
{"name": "Charlie", "age": 35, "role": "admin"}
])
; Filter by role (fields auto-bind)
(where users (= role "admin"))
; Result: [{"name": "Alice", ...}, {"name": "Charlie", ...}]
; Complex conditions
(where users (and (>= age 30) (= role "admin")))
; Result: [{"name": "Alice", ...}, {"name": "Charlie", ...}]
Key feature: In where predicates, field names automatically bind to the current object’s fields—no need for explicit path notation.
Transform — Data Shaping
; Pick specific fields
(transform users (pick "name" "email"))
; Result: [{"name": "Alice", "email": "..."}, ...]
; Add computed fields
(transform products (assign "discounted" (* price 0.9)))
; Remove sensitive fields
(transform users (omit "password" "ssn"))
; Rename fields
(transform users (rename "email" "contact_email"))
Operations:
pick: Select fields to keepomit: Select fields to removeassign: Add/compute new fieldsrename: Change field names
Composition
Operations naturally compose:
; Find expensive products, extract names
(pluck
(transform
(where products (> price 50))
(pick "name" "price"))
"name")
; Pipeline style
(do
(def expensive (where products (> price 50)))
(def simplified (transform expensive (pick "name" "price")))
(pluck simplified "name"))
Resource Management
JSL includes comprehensive resource management for safe distributed execution:
Gas Metering
Track computational cost:
runner = JSLRunner(config={
"max_gas": 10000,
"gas_costs": {
"+": 1,
"*": 2,
"lambda": 10,
"apply": 5
}
})
result, state = runner.execute_partial(expensive_program)
print(f"Gas consumed: {state.gas_consumed}")
print(f"Gas remaining: {state.gas_remaining}")
Step Limiting
Bound execution steps:
runner = JSLRunner(config={"max_steps": 1000})
result, state = runner.execute_partial(program)
if state: # Paused at step 1000
# Can inspect state
print(f"Paused at step {state.step_count}")
# Resume with more steps
final = runner.execute_partial(program, state=state, max_steps=1000)
Fair Scheduling
Allocate resources fairly:
programs = [prog1, prog2, prog3]
states = [None, None, None]
# Round-robin execution
for i in range(100): # 100 rounds
for j, prog in enumerate(programs):
result, states[j] = runner.execute_partial(
prog,
state=states[j],
max_steps=100 # 100 steps per round
)
Content-Addressable Serialization
JSL uses content-addressable storage for efficient serialization of complex object graphs:
Simple Values
# Simple values serialize directly
value = [1, 2, {"a": "hello"}]
serialized = runner.serialize_value(value)
# {"result": [1, 2, {"a": "hello"}]}
Complex Values with Closures
# Closure with captured environment
runner.execute('(def x 10)')
runner.execute('(def f (lambda (y) (+ x y)))')
closure = runner.env.get('f')
serialized = runner.serialize_value(closure)
# Result uses content-addressable format:
{
"__cas_version__": "1.0",
"root": "hash_of_closure",
"objects": {
"hash_of_closure": {
"type": "closure",
"params": ["y"],
"body": ["+", "x", "y"],
"env": "hash_of_env"
},
"hash_of_env": {
"type": "env",
"bindings": {"x": 10},
"parent": null
}
}
}
Benefits:
- Deduplication: Identical objects stored once
- Circular references: Handled naturally via hashes
- Efficiency: Large environments shared across multiple closures
- Deterministic: Same value → same hash
Security Model
JSL’s security is based on capability restriction:
No Arbitrary Code Execution
JSL doesn’t compile to native machine code. All execution happens within the JSL interpreter.
Host-Controlled Capabilities
All side effects go through the host:
; This produces a data structure, doesn't execute directly
(host file-write "/tmp/output.txt" "data")
; Host decides whether to allow this
Host implementation:
class SecureHost:
def handle_effect(self, effect):
command = effect["command"]
if command == "file-write":
path = effect["args"][0]
# Check permissions
if not self.is_allowed_path(path):
raise PermissionError(f"Cannot write to {path}")
# Execute if allowed
return self.do_file_write(path, effect["args"][1])
raise ValueError(f"Unknown command: {command}")
Sandboxing
JSL programs run within the interpreter’s sandbox:
- ✅ No system calls
- ✅ No file access (unless host allows)
- ✅ No network access (unless host allows)
- ✅ Bounded computation (via gas/steps)
Inspectable Effects
Effect requests are JSON data:
# Log all effect requests
effects = []
class AuditingHost(Host):
def handle_effect(self, effect):
effects.append(effect) # Log before executing
return super().handle_effect(effect)
Architecture Layers
- Wire Layer: JSON representation for transmission/storage
- Compilation Layer: Parser + JPN compiler/decompiler
- Runtime Layer: Dual evaluators + environment management
- Prelude Layer: Standard library (arithmetic, lists, strings, etc.)
- User Code Layer: JSL programs and libraries
- Host Interaction Layer: Effect requests as data
- Host Environment: Capability control and execution
Theoretical Foundations
Homoiconicity
Like Lisp, JSL code and data share the same structural representation. However, JSL uses JSON arrays/objects instead of S-expressions:
; Lisp S-expression
(+ 1 2 3)
; JSL JSON array
["+", 1, 2, 3]
This enables:
- Code generation (programs that write programs)
- Macros (code transformations)
- Metaprogramming (inspect/modify code as data)
Lexical Scoping and Closures
(do
(def make-adder (lambda (n)
(lambda (x) (+ x n))))
(def add5 (make-adder 5))
(def add10 (make-adder 10))
(add5 3) ; 8
(add10 3) ; 13
)
Both add5 and add10 are closures that captured different values of n from their lexical environment.
Functional Programming
JSL encourages:
- Immutability: Values don’t change
- First-class functions: Functions as values
- Higher-order functions: Functions that take/return functions
- Expressions over statements: Everything returns a value
Separation of Pure Computation and Effects
; Pure computation (deterministic)
(def sum (lambda (xs) (fold + 0 xs)))
; Effect request (non-deterministic)
(host http-get "https://api.example.com/data")
Pure computations can be:
- Cached (memoization)
- Parallelized
- Tested deterministically
- Optimized by compiler
Use Cases
Distributed Computing
# Send computation to where data lives
computation = ["map", ["*", 2], data_on_remote_server]
# Serialize and send
send_to_server(runner.serialize_value(computation))
# Server executes and returns result
Serverless Functions
# Define function as JSON
function = {
"code": ["lambda", ["event"],
["transform", "event", ["pick", "user", "action"]]],
"max_gas": 1000,
"max_steps": 100
}
# Store in database, execute on demand
API Query Language
# Safe user-provided queries
user_query = {
"filter": ["where", "products", ["and",
[">", "price", 10],
["=", "category", "electronics"]]],
"transform": ["transform", "$", ["pick", "name", "price"]],
"limit": 10
}
# Execute with resource limits
result = runner.execute_safe(user_query, max_gas=1000)
Edge Computing
# Deploy logic to edge devices
edge_function = ["lambda", ["sensor_data"],
["if", [">", ["get", "sensor_data", "temperature"], 100],
["host", "alert", "High temperature!"],
["host", "log", "Normal"]]]
# Update dynamically without redeployment
send_to_edge_device(runner.serialize_value(edge_function))
Plugin Systems
# Users extend application with sandboxed plugins
plugin = {
"name": "markdown-formatter",
"code": ["lambda", ["text"],
["host", "markdown-to-html", "text"]],
"permissions": ["markdown-to-html"],
"max_gas": 5000
}
# Load and execute with restrictions
runner.load_plugin(plugin)
Smart Contracts
# Verifiable computations with gas metering
contract = {
"code": ["lambda", ["from", "to", "amount"],
["if", [">", ["balance", "from"], "amount"],
["do",
["update-balance", "from", ["-", ["balance", "from"], "amount"]],
["update-balance", "to", ["+", ["balance", "to"], "amount"]],
true],
false]],
"max_gas": 1000 # Prevent infinite loops
}
Real-World Examples
ETL Pipeline
(do
; Load data
(def raw-data (host file-read "sales.jsonl"))
; Filter completed sales
(def completed (where raw-data (= status "completed")))
; Transform to summary format
(def summary (transform completed
(pick "customer_id" "amount" "date")))
; Group by customer and sum
(def totals (group-by summary "customer_id"
[(sum "amount")]))
; Save results
(host file-write "totals.json" totals))
Workflow Automation
(def workflow (lambda (event)
(do
(def user (host db-get "users" (get event "user_id")))
(if (= (get user "role") "admin")
(do
(host log "Admin action detected")
(host send-email (get user "supervisor") "Admin action log"))
(host log "Regular user action"))
(host update-stats event))))
Data Validation
(def validate-user (lambda (user)
(and
(has-path user "email")
(str-matches (get user "email") "^[^@]+@[^@]+\\.[^@]+$")
(> (get user "age") 0)
(< (get user "age") 150))))
(def valid-users (where users validate-user))
Prelude Functions
JSL includes a rich standard library:
Arithmetic
+, -, *, /, %, =, <, >, <=, >=
Lists
cons, first, rest, map, filter, fold, take, drop, length
Strings
str-concat, str-matches, str-replace, str-find-all, str-length
Objects
get, get-path, set-path, has-path, keys, values
Query
where, transform, pluck, index-by, group-by
Logic
and, or, not, if
Functional
lambda, apply, map, filter, fold
Quick Start
# Install
pip install jsl-lang
# Interactive REPL
jsl --repl
# Evaluate expression
jsl --eval '["+", 1, 2, 3]'
# Run file
jsl program.jsl
Python usage:
from jsl import JSLRunner
runner = JSLRunner()
# Execute program
result = runner.execute('(+ 1 2 3)') # 6
# Define and use functions
runner.execute('''
(do
(def double (lambda (x) (* x 2)))
(map double (@ [1 2 3 4 5])))
''') # [2, 4, 6, 8, 10]
# Query and transform data
runner.execute('(def users [@ {...}, {...}])')
admins = runner.execute('(where users (= role "admin"))')
Design Philosophy
📦 Network-Native: JSON serialization is not bolted on—it’s the foundation
🔒 Secure by Design: Host controls all capabilities, effects are data
🔄 Resumable: Pause and resume computation anywhere
⚡ Efficient: Dual evaluators for different performance characteristics
🎯 Deterministic: Pure computation enables testing and optimization
🧩 Homoiconic: Code is data, data is code
📊 Resource-Bounded: Gas metering and step limiting built-in
Comparison with Other Languages
| Feature | JSL | Python | JavaScript | Scheme |
|---|---|---|---|---|
| Serializable closures | ✅ | ❌ | ❌ (limited) | ❌ |
| Resumable execution | ✅ | ❌ | ❌ | ❌ |
| Gas metering | ✅ | ❌ | ❌ | ❌ |
| Effect reification | ✅ | ❌ | ❌ | ❌ |
| JSON-native | ✅ | ❌ | ✅ (values) | ❌ |
| Network transmission | ✅ | Via pickle (unsafe) | Via eval (unsafe) | ❌ |
| Sandboxing | ✅ | Limited | Limited | ❌ |
Resources
- PyPI: pypi.org/project/jsl-lang/
- Repository: github.com/queelius/jsl
- Documentation: Full API docs and tutorials
- Examples: See
examples/directory
License
MIT
JSL: Where code becomes data, and data becomes mobile. A network-native functional language designed for the distributed computing era.
Discussion