簡素

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 main'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; err reason and none are ordinary types that propagate on their own. an err reaching main unhandled is a compile error, not a crash.

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; fields, imports, 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, recipe in the repo
# kanso vs serde_json: remeasured by ci on every merge —
# the live board is on the compiler page. no lifetimes, no gc.
# reasonably-written rust   1.02
# go encoding/json          1.95

the speed is also a golden file. the gauntlet performs exactly 5,334,608 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; 151 beat iterations is one rewind per decode plus one for the program itself, on the record. 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 reproduce-it-yourself harness: both scoreboards and chapter 07.

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)

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

literal 0 outranks the generic overload—the base case is dispatch, not a conditional. and int is arbitrary-precision by design, so 20! just works — the interpreter is exact today and the native build refuses rather than wrapping while its bignum arithmetic is built.

examples/records.kso
type user
  admin:bool
  name:string

main =
  clay = user true "clay"
  print (welcome clay)

fn welcome (user _ name)
  "irasshaimase, {name}"
output
irasshaimase, clay

every type is a single-constructor record; fields are alphabetical, enforced. construction is positional and destructuring is how you take records apart.

examples/effects.kso
main = print "one" >> print "two" >> print "three"
output
one
two
three

>> sequences descriptions without passing data. together with ., it 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}"

main =
  prices = [30 10 20]
  doubled = map prices (x -> x * 2)
  ninth = prices[9]
  print "sorted: {sort prices}" >> print "doubled: {doubled}" >> print (describe ninth)

one import enrolls std/list's names into the file's overload space—map and sort arrive bare, and dispatch resolves shared names by specificity, the same mechanism as everything else. no panicking access anywhere: prices[9] returns none, which propagates until a describe overload dispatches on it (assert presence with prices[9]! and a miss errs instead). indexing is 1-based, everywhere, no exceptions.

examples/custom_render.kso
type money
  cents:int

pub play =
  price = money 350
  print "that costs {price}"

fn to_string (money cents)
  "${cents / 100}.{cents - (cents / 100 * 100)}"
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—eleven 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.

two engines

past serde_json with no lifetimes at all

a reference interpreter (the semantics oracle—it also runs in your browser on the playground) and an llvm-backed native compiler, 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 frozen build-block cohorts—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)
  • an eleven-chapter 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