簡素

kanso

the language where the source contains only decisions.

examples/pipes.kso
output
hello, kanso
open in the playground →
the founding principle
anything a style guide, linter, or code review would enforce by convention, kanso enforces by making the alternative a compile error or unrepresentable.
doctrine

six deletions

kanso is defined less by what it adds than by what it makes unnecessary. every derivable fact lives in tooling; the source keeps only what the compiler cannot know.

01

effects are values

print "hi" returns a description of printing; nothing executes until the runtime receives the program's description. effect sets are inferred and propagate up the call graph—no async/await, no function coloring, zero overhead for pure calls. tests assert on descriptions; no mocks.

02

failure is a value

no exceptions, no panics—and no ok/some wrappers either. success is the bare value. an outcome the caller handles is an ordinary value, none or a marker, and a failure is an err that rides past every function to the endpoint, which reports where it was born and everywhere it passed.

03

dispatch is the only eliminator

values may inhabit typesets, and the only way out is dispatch. no match, no instanceof, no tag tests, no narrowing syntax. overloads dispatch on literals, concrete types, or typeset guards—resolved statically, and monomorphic code carries no tags.

04

no interfaces

no interface, class, or instance construct exists. a generic function's requirements are inferred from its body—minimal by construction, transitive through call chains. the lsp renders the effective contract; publish tooling diffs it and enforces semver.

05

canonical form

one rendering per program. non-canonical whitespace is a syntax error; imports, keyed reads and typeset members are alphabetical, enforced. no formatter tool exists—there is nothing left to format.

06

memory without gc or borrow checker

values are immutable, so the heap is a dag, so reference counting is complete. perceus-style, after koka, lean 4, and roc—kanso's no-shadowing and nothing-wasted rules make the same analyses more complete. no annotations, no rejected programs, no memory syntax in source; where uniqueness can't be proven, a reuse site pays one predictable branch (closeable per-function by opt-in fip).

receipts

purity is not a tax

the same discipline that deletes the footguns hands the compiler everything rust asks the programmer to manage. on the json gauntlet—one 188 kb document, decoded 150 times—the pure language outruns the parser rust hand-tunes:

ms of cpu per decode—m-series mac, interleaved per-decode slopes, sat 2026-08-07, recipe in the repo
# kanso vs serde_json: the live board is on the compiler page,
# and the decoder has moved since this sitting. no lifetimes, no gc.
# reasonably-written rust   1.04
# go encoding/json          2.05

the speed is also a golden file. the gauntlet performs exactly 1,125,014 allocations—the same count on every run, on every machine, on both architectures—and ci diffs it like program output. 2 arena blocks across 150 decodes is the flat-memory guarantee as a constant; 150 beat iterations are on the record beside them. in a deterministic language a performance regression shows up as a failing diff in the pull request that caused it. and the counters now count laziness too: kanso computes a value when it's demanded, or never, and on a workload where one value in twenty is used, the counters read thunk_allocs=100000, thunk_evals=5000—95% of the work never born, the clear form running at hand-tuned-rust speed. the mechanics, with the harness to reproduce them, are on the compiler page beside both scoreboards.

real programs

see it run

every sample below is verbatim from examples/ in the repository.

examples/dispatch.kso
fn fact 0
  1

fn fact n
  n * fact (n - 1)

answer = fact 20
print "20! = {answer}"
output
20! = 2432902008176640000

the literal 0 outranks the generic arm, so the base case is an overload and the recursive case needs no if. int has no ceiling on any engine, so 20! prints all nineteen digits.

examples/records.kso
type employee
  age
  name
  salary

type user
  age
  name

fn welcome (employee _ name _)
  "irasshaimase, {name}"

clay = user 47 "clay"
user age name = clay
worker = employee age name 90000
print (welcome worker)
output
irasshaimase, clay

every type is a record with one constructor. its fields come in the order you declare them, construction is positional in that order, and user age name = clay takes one apart by the same shape. a function head can do the same, ignoring what it does not need with _.

examples/effects.kso
print "one" .> (_ -> print "two") .> (_ -> print "three")
output
one
two
three

.> hands a description's value to the next step, and _ -> ignores it when the step only needs to come after. that is the complete vocabulary of "before"—everywhere else, the runtime owns order.

examples/lists.kso
import "std/list"

fn describe none
  "no ninth price"

fn describe x
  "ninth price: {x}"

prices = [30 10 20]
doubled = list/to_list (list/map prices (x -> x * 2))
ninth = prices[9]
print "sorted: {list/sort prices}"
  .> (_ -> print "doubled: {doubled}")
  .> (_ -> print (describe ninth))
output
sorted: [10 20 30]
doubled: [60 20 40]
no ninth price

the list functions live in std/list and are called by their module's name. list/map answers a recipe that runs when something consumes it, and list/to_list is the consumer that makes it a list. an index past the end is not a crash: prices[9] answers none, and a describe arm takes it. a program that knows the element is there writes prices[9]!, and a miss is then a failure that reaches the top with where it was born. indexing starts at 1.

examples/custom_render.kso
type money
  cents

fn to_string (money cents)
  "${cents / 100}.{cents - cents / 100 * 100}"

price = money 350
print "that costs {price}"
output
that costs $3.50

string interpolation is dispatch on an ordinary to_string group—so a custom rendering is one arm for a type you own, no import, no interface, no registration. nobody can re-render int (no module owns both the group and the primitive), so "{42}" means one thing in every program ever compiled—and that guarantee doubles as the license to skip dispatch entirely on primitive-only paths.

the scheduler works the red lights
# a 2000 ms wait beside ~900 ms of pure work — measured, both engines
# sequential (stall, then work)     2913 ms
# overlapped (work inside stall)    2008 ms   the grind disappeared

# the scheduler credits real compute time against every pending wait.
# the transcript stays purely logical: same seed, same output, same
# counters, on every machine — only the physical wait shrinks.

the work-ahead ruling extends this: the strictness analyzer already proves which computations are certain to run, so a parked fiber's wait can be filled with proven work automatically—two rules, one sort key, no annotations. wall-credit is shipped; the pool that finds the overlap by itself is the frontier. the picture, the ladder, and the rig: the compiler page.

status

where things stand

v0.1 design freeze

the book is the canon

the kanso book is the language, taught in order, in chapters where every code panel is executed against the real toolchain before it may appear on the page. what the book does not yet cover has not been decided yet.

three engines

past serde_json with no lifetimes at all

a reference interpreter (the semantics oracle), an llvm-backed native compiler, and the browser engine the playground runs, which compiles to webassembly in the tab through the native compiler's emitter, held byte-identical across the golden corpus by differential ci—floats included, because every engine renders the shortest round-trip. memory is two tiers with no garbage collector in either: strict values live on rewinding arenas (the shipped model behind the json numbers—neck and neck with hand-tuned serde_json per 188 KB decode at the smaller footprint, remeasured by ci on every merge) and deferred values live in refcounted cells the demand analysis creates only where laziness can pay. the ratified destination runs further: kanso's data graph is immutable, and acyclic except inside the nodes one list/tie makes—a cycle counts as one unit, so reference counting stays complete and the arenas survive as an optimization inside a counted world. the story, the receipts, and both scoreboards: the compiler page.

real software

libraries and tools exist

  • kanso-json—the decoder that edges serde_json, at the smallest footprint on the board
  • kq—a jq-style query cli, byte-identical to jq -S on the 188 KB benchmark document, 1.7× jq on path extraction (25/25 interleaved heats)
  • the book—every sample executed before it was printed
  • a repl (kanso repl) with it0-history, in your terminal and on the playground
quickstart

run it

clone, build, run. one binary carries the whole toolchain—interpreter, native compiler, test runner, package manager—and chapter 01 of the book starts here.

shell
$ git clone https://github.com/kanso-lang/kanso
$ cd kanso
$ cargo build --release
$ ./target/release/kanso run examples/hello.kso
hello, world

editors: one textmate bundle highlights .kso in jetbrains ides and vs code—the grammar itself is kanso.tmLanguage.json.

enso-neko - sleeps in a circle she never quite closes
ensō-neko—sleeps in a circle she never quite closes