Skip to content

Why :script Beats Python and Nim Where It Actually Matters

Why :script Beats Python and Nim Where It Actually Matters

Section titled “Why :script Beats Python and Nim Where It Actually Matters”

Most scripting languages promise speed of writing.

Janus :script promises something rarer:

Speed of writing that survives contact with production.

Not throwaway code. Not rewrite-later code. Promotable code.


Python scripts grow into systems. Bash scripts grow into nightmares. Ruby scripts grow into memory hogs. Nim scripts grow into clever but fragile experiments.

Every ecosystem repeats the same pattern:

  1. Write something quick.
  2. It becomes important.
  3. Rewrite it in something safer.
  4. Maintain two versions forever.

That rewrite tax is the real cost of scripting. Janus was designed to remove it.


A :script file is not a toy language. It is :core with bounded sugar. Nothing more.

Every script can be converted mechanically into production code. No rewriting. No porting. No reinterpretation. Just:

Terminal window
janus desugar script.jans script.jan

That transformation is guaranteed to produce valid :core code. Not similar code. Not equivalent code. The exact same program, without sugar.

This rule is non-negotiable. It is the foundation of the system. We call it The Script Law:

Every :script program must be transformable into a syntactically valid :core program by janus desugar. The transformation is purely additive; no construct in :script exists that cannot be expressed in :core. Promotion never requires a rewrite.


Python is excellent for exploration. But fragile for operations. The problems are structural, not cultural.

In Python: typo a regex capture → runtime failure. Pass the wrong type → runtime failure. Forget an argument → runtime failure.

In Janus: named captures are typed. Mismatches are compile-time errors. Pipelines fail before execution.

You discover errors when writing code. Not when deploying it.

Python scripts always pay runtime tax. Startup latency. Memory floor. Runtime interpretation.

Janus scripts compile once, then cache. Second run: native binary. No interpreter. No runtime penalty. Cache-hit execution is dominated by kernel execve time, not language startup.

Python scripts scale poorly. Not because Python is slow. Because structure disappears. Small scripts become tangled imports, global state, hidden side effects. Promotion to production means rewriting into something stricter.

Janus removes that step. Scripts become systems without changing languages.


Nim is the closest competitor. Compiled. Expressive. Fast. But it solves a different problem.

Nim optimizes for language cleverness. Janus optimizes for semantic discipline. That difference matters over time.

Macros. Metaprogramming. Compile-time tricks. These are powerful. They are also dangerous. Complex systems become hard to reason about. Not because Nim is flawed. Because freedom scales badly.

Every convenience in :script is:

  • Bounded — the sugar list is closed and fixed in the spec
  • Visiblejanus desugar shows exactly what compiles
  • Reversible — promotion is mechanical, not a rewrite

Nothing exists that cannot be expressed in :core. Nothing hides execution behavior. Nothing invents authority. That constraint is the advantage.


Not syntax. Not performance. Not ergonomics.

Promotion without rewriting.

That is the breakthrough. Every :script program is mechanically transformable into production code. Always. By rule. Not by convention.


Traditional pipelines:

Terminal window
grep ERROR log.txt | awk '{print $5}' | sort | uniq

Work. Until they don’t. Failures appear late. Errors are silent. Types do not exist.

Janus pipelines:

<<p"app.log">>
|> grep(r/ERROR/)
|> map($_.fields())
|> group_by($5)
|> count()

Are:

  • Type-checked — capture groups are struct fields, misspellings are compile errors
  • Fused into one pass — adjacent operators compile to a single iterator walk, zero allocations between stages
  • Allocation-minimal — pipeline fusion is proven by the type system
  • Provenance-aware — every line carries source path + line number through arbitrary transformations

Most scripting bugs are quoting bugs. Most outages start with string interpolation.

Janus does not pass shell commands as strings. It parses them. Before execution.

let result := `ls -la /etc`
`gzip ${log_path}`? // execute, propagate error

Backtick-delimited shell literals are tokenized at parse time, not by a shell. Argument splitting follows POSIX-like rules but is performed by the Janus parser, not by /bin/sh. There is no shell injection by default because there is no shell.

Interpolation via ${expr} produces a single argument per interpolation, regardless of whitespace. `rm ${user_input}` is safe even when user_input contains spaces or shell metacharacters.

Memory Discipline Without Garbage Collection

Section titled “Memory Discipline Without Garbage Collection”

Python and Ruby depend on garbage collectors. That introduces unpredictable pauses, invisible memory behavior, hidden lifetime costs.

Janus scripts use scoped allocation. A script arena allocator is injected at entry to the synthetic main function. Temporary memory dies when scope ends. Not later. Not unpredictably. Immediately.

The implicit binding extends one stack frame. A function f called from main sees the ambient allocator. A function g called from f does not, unless f explicitly passes it. This preserves cost visibility and prevents ambient allocator pollution.

After transformation. After filtering. After aggregation. Most systems lose origin data.

Janus preserves it. Every Line in a TextStream carries provenance: source path, line number, byte offset. You never ask “Where did this come from?” You already know.


Modern scripts are often written by AI. That changes the rules.

Languages with multiple ways to do one thing, hidden runtime behavior, ambiguous semantics confuse agents. Janus intentionally limits choice. Not to reduce power. To reduce error space.

One operation. One mechanism. One visible cost. That is AI-legible code.

An agent writing Ruby has 12 ways to read a file, 4 string-formatting systems, and a method_missing runtime that can hide anything. An agent writing :script has one way to do each thing, by design. The search space is the smallest of any scripting language because we banned the duplicates that other languages accumulated by accretion.


  • Machine learning
  • Scientific computing
  • Notebooks
  • Web frameworks
  • Data science ecosystems

That will not change quickly. Nor should it. Janus does not replace Python everywhere. It replaces Python where correctness matters more than convenience.

  • Compile-time metaprogramming
  • Embedding scripting into applications
  • Macro-heavy DSL creation

Those are real advantages. Janus targets a different space: disciplined operational systems.


Janus :script is not a Python replacement. It is not a Nim replacement. It is something else.

A scripting layer that does not collapse when it becomes important.

That difference is subtle. Until the first time a script survives production.


Quick to write.
Compiles once. Runs forever.
Grows without rewrite.


Terminal window
# Create a script
echo 'println("Hello from Janus")' > hello.jans
# Run it (compiles, caches, executes)
janus hello.jans
# See what it compiles to
janus desugar hello.jans
# Promote to production
janus desugar --to=core hello.jans

The :script profile ships with the Janus toolchain. No extra installation. No separate runtime. Just janus run.


Ready to write scripts that survive production? Start with the :script profile guide →