Where contracts meet effects

A JVM language with algebraic effects, runtime specs, and property-based contracts

v0.8.4JVMVirtual ThreadsANTLR4

Algebraic Effects

First-class effect system with deep handlers. Mock any side effect in tests. State-machine bytecode lowering for hot perform-loops.

Parametric Effect Rows

Higher-order fns declare row variables: fold :: (Fn):eff _ _ _ ::: eff. The callback's effect row binds eff and propagates into the caller's row at each call site. Compile-time enforcement, no runtime erasure.

Runtime Specs

Malli-inspired specs replace types. Certify values at construction, validate at boundaries. Spec-annotated pub fns are mandatory; lint enforces it.

Contracts

Pre/post conditions on functions, in/out contracts at module boundaries. Blame tracking — pre-failure accuses the caller, post-failure accuses the implementation.

Structured Concurrency

scope/fork/await, par, race, timeout. Java virtual threads with auto-cancellation. Spawned fibers inherit effect row + session namespace.

Pipeline Operators

|> pipeline, @ map, /? filter, /+ fold. >> composition, ~ apply-to-rest.

Module System & Packages

use mod.name :open / :as alias / { selective }. Git deps via irij.toml. Pub/private exports. Effect-gated APIs.

Bytecode-only Execution

irij build emits a self-contained JAR. State-machine handler lowering is the single execution path — no interpreter, no threaded fallback, no ambiguity.

Hot Redef + Java Interop

REPL-driven fn redefinition via invokedynamic + MutableCallSite. Java classes via Class/member, dot-access, ::: JVM capability tracking.

Code Examples

Specs & Pattern Matching

spec Shape
  Circle Float
  Rect Float Float

fn area :: Shape Float
  Circle r   => 3.14159 * r * r
  Rect w h   => w * h

println (area (Circle 5.0))
;; 78.53975

Algebraic Effects

effect Logger
  log :: Str -> ()

handler console :: Logger ::: Console
  log msg =>
    println msg
    resume ()

with console
  log "Starting..."
  log "Done!"

Parametric Effect Rows

;; Row-variable `:eff` binds the callback's effects
;; and propagates them to the caller's effect row.
fn fold :: (Fn):eff _ Vec _ ::: eff
  (f acc xs -> ...)

;; Pure use — no extra row at the call site.
total := fold (+) 0 #[1 2 3 4 5]

;; Effectful callback — Console flows in automatically.
fn shout ::: Console
  =>
  sum := fold (acc n -> println n ; acc + n) 0 #[1 2 3]
  println sum

Contracts (pre / post / in / out)

fn withdraw :: Account Int Account
  pre  (account amt -> amt > 0)
  post (result -> result.balance >= 0)
  (account amt ->
    {...account balance= account.balance - amt})

;; Blame-tracking: pre-fail accuses caller,
;; post-fail accuses the fn itself.

Pipelines & Seq Ops

nums := #[1 2 3 4 5 6 7 8 9 10]

result := nums
  |> /? (n -> n > 3)
  |> @ (n -> n * n)
  |> /+

println result
;; 355

Structured Concurrency

scope s
  a := s.fork (-> fetch "users")
  b := s.fork (-> fetch "posts")
  merge (await a) (await b)

;; Composable combinators
sum := par (+) (-> slow-calc 1) (-> slow-calc 2)
winner := race (-> slow) (-> fast)
ok := timeout 1000 (-> work)

Functions

;; Three fn body styles:

fn greet ::: Console            ;; imperative
  => name
  msg := "Hello, " ++ name
  println msg
  msg

fn add (x y -> x + y)           ;; lambda

fn fib
  0 => 1                        ;; match arms
  n => n * fib (n - 1)

Collections & Records

vec := #[1 2 3]
set := #{:a :b :c}
tup := #(42 "hello")
person := {name= "Alice" age= 30}

;; Immutable update
older := {...person age= 31}

;; Mutable opt-in
counter :! 0
counter <- counter + 1

Protocols

proto Show a
  show :: a -> Str

impl Show for Int
  show := (n -> to-str n)

impl Show for Vector
  show := (v -> "[" ++ (join ", " (@ to-str v)) ++ "]")

println (show 42)
println (show #[1 2 3])

Roadmap

Irij is in active development. Current status:

donePhase 0-2 Parser, AST, REPL, CLIdonePhase 3 Algebraic effects with deep handlersdonePhase 4 Module system + standard librarydonePhase 5 Structured concurrency (virtual threads)donePhase 6 Contracts, blame trackingdonePhase 7 Effect row declarations + enforcementdonePhase 8 Runtime specs, validation, lintdonePhase 9 Package management (deps.irj, git deps)donePhase 10 I/O: JSON, FileIO, HTTP client + serverdonePhase 11 Database effect (SQLite, std.db)donePhase 12 Package registry app + PlaygrounddonePhase 13 Java interop (Class/member, dot-access)donePhase 14 Bytecode compiler: state-machine effects, hot-redef, JVM capabilitydonev0.5.0 Effect-row soundness; full bytecode spec parity; pre/post contracts in bytecodedonev0.5.x Bytecode-mode parity for the full irij.online website; Irij source line numbers in JVM stack tracesdonev0.6.0 Parametric effect rows ((Fn):eff / (_ -> Bool):eff / ::: eff)donev0.6.13 Interpreter package + threaded handler lowering removed. Single execution model: state-machine bytecode.donev0.6.15 Spawn inherits namespace + per-session stdout (Playground cross-eval fn refs + spawn output capture). SM lowering: composed handlers, destructure pre-op binds.donev0.7.0 std.auth seed (sha256, hmac, random-token); SM-lowering correctness gaps closed; dev.irij.interpreter renamed to dev.irij.runtime; release auto-tagdonev0.7.1 Capabilities replace raw-* surface (Db, Http, Serve, FileIO, Session); Irij-record caps; named record specs with row params; inline `with do …`; LSP MVP (irij lsp)donev0.8.0 Handler:Effect specs (first-class handler values, dynamic-handler typing); LSP 2b.1 (symbol-aware hover, goto-def, identifier completion, effect-row diagnostics)donev0.8.1 Per-request HTTP handlers inherit caller's effect stack (SSE / multipart now work); `try` traps every Throwable (division by zero, casts, NPEs); GraalVM deferred to techdebtdonev0.8.2 Patch: empty-body HTTP responses send -1 length cleanly (redirects, 204s, errors from `irij publish`)donev0.8.3 `Map` spec accepts records (Tagged with namedFields); vrata-style `El`/`Node` flow through `:: Map` boundaries without ceremonydonev0.8.4 `with` + tail `if/else` returns the branch value (EffIR lowering); snake_case identifiers lex as one IDENT (SQL/JSON field names)doneMulti-tenant registry Users, sessions, API tokens, per-owner ownership; auth-gated publish; Datastar dashboard with token CRUDnextchereda Queue effect (Stream a) + SQLite cap; first opt-in seed in irij/ namespaceplannedchereda-redis Redis backend for chereda; opt-in jedis dep gated by seed importplannedbutterfly Reactive streams on single-resume effects; map/filter/scan/take/merge operators; chereda-backed cross-stream opsplannedFuture Linear types (capability + channel safety); channels (send/recv/select); choreographic programming; refinement types; content-addressed code; multi-resume effects (search/nondet/probabilistic); GraalVM native image