Skip to main content

Experimental

Epsil

A programming language for scientific computing

Try it

This runs in your browser. Edit the source and re-run it.

⌘/Ctrl + Enter

Expressive

Pipelines, lambdas and multi-clause definitions carry a computation from input to result, in the order you would describe it out loud. Conditionals, matches and blocks all produce values, so any of them can feed the next step.

Take the tour
1..10
|> Filter(n |-> n % 2 == 0)
|> n |-> n^2
|> Sum
// ➔ 220

Symbolic by default

Expressions stay exact until you ask for a number. Simplify, differentiate and solve without leaving the language.

How evaluation works
let e = Simplify((x^2 - 1) / (x - 1))
// ➔ x + 1
Solve(e == 6, x)
// ➔ [5]

Math you can read

Implicit multiplication, superscripts, ranges and LaTeX islands — notation that matches what you would write on paper.

Read the syntax
2x + 3x^3 // 2 * x + 3 * (x^3)
$\sqrt{\frac1n}$ // √(1/n)

Units and uncertainty

Quantities carry their units through a computation and reconcile as they go, while measurements propagate their error — 10 ± 0.1 m by 250 ± 2 cm is 25.00 ± 0.32 m².

See it work
let L = Measurement($10\,\mathrm{m}$, 0.1)
let W = Measurement($250\,\mathrm{cm}$, 2)
N(UnitConvert(L * W, $\mathrm{m^2}$))
// ➔ (25.00 ± 0.32) m^2

Types that describe your data

Type inference covers most code, so annotations are for when the shape itself matters. Recursive unions, generics with bounds, structural aliases for convenience and nominal types so a celsius never passes for a fahrenheit — all checked before anything runs.

How types work
type alias json = number | string | boolean
| missing | list<json> | dictionary<json>

let doc: json = {"tags" -> ["math", "computing"]}
let bad: json = x |-> x // rejected before it runs

Values never change

A value is immutable; a binding is the part that moves. Sort, Append and Join all hand back something new. Functional, but not dogmatic about it: when a mutable local and a for loop read better, take them.

Values and bindings
let xs = [3, 1, 2]
let ys = Sort(xs)
(xs, ys)
// ➔ ([3, 1, 2], [1, 2, 3])

Effects are in the signature

Console, network, filesystem, randomness and six more are tracked. Effects are inferred from a body, and a written specifier becomes a contract it has to keep.

Effect specifiers
function roll(xs) random -> integer { Random(xs) }
function double(x) pure -> number { 2x }

// Declaring the first one pure is rejected: its
// body performs an effect it did not admit to.

Errors are values

A failed computation produces a value that flows through the rest of the work, not an exception that unwinds your program.

Control flow
Map([16, -4, "banana", 81], x |-> Sqrt(x))
// ➔ [4, 2i, NaN, 9]

Built for humans, tuned for agents

A small, unambiguous grammar with a machine-readable spec, an MCP server and a CLI, so tools can generate and check it.

Epsil for AI agents

Use it from JavaScript

import { ComputeEngine, executeEpsil } from
  "@cortex-js/compute-engine/epsil";

const ce = new ComputeEngine();
const { value, diagnostics } = executeEpsil(ce, "1 + 2");

There is also a command-line REPL, a VS Code extension and an MCP server.