Skip to main content

JSL: A Network-Native Functional Language Where Code Is JSON

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 Closure object 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

  1. Linear Execution: Sequential instruction processing
  2. Natural Resumption: Stack state captures exact execution point
  3. Efficient Serialization: Flat array vs nested trees
  4. Cache-Friendly: Sequential memory access
  5. 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 keep
  • omit: Select fields to remove
  • assign: Add/compute new fields
  • rename: 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

  1. Wire Layer: JSON representation for transmission/storage
  2. Compilation Layer: Parser + JPN compiler/decompiler
  3. Runtime Layer: Dual evaluators + environment management
  4. Prelude Layer: Standard library (arithmetic, lists, strings, etc.)
  5. User Code Layer: JSL programs and libraries
  6. Host Interaction Layer: Effect requests as data
  7. 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

FeatureJSLPythonJavaScriptScheme
Serializable closures❌ (limited)
Resumable execution
Gas metering
Effect reification
JSON-native✅ (values)
Network transmissionVia pickle (unsafe)Via eval (unsafe)
SandboxingLimitedLimited

Resources

License

MIT


JSL: Where code becomes data, and data becomes mobile. A network-native functional language designed for the distributed computing era.

Discussion