anything a style guide, linter, or code review would enforce by convention, kanso enforces by making the alternative a compile error or unrepresentable.
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.
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.
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.
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.
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.
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.
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).
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:
# 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.
every sample below is verbatim from examples/ in the repository.
safe_ratio 10 0 answers divide_by_zero—division by zero is a value, not a panic. describe dispatches on it: one overload for that type, one for its parent math_failure, one for the bare number. no wrapper to unwrap, no exception to catch.
# Division by zero answers a value you dispatch on rather than a failure you
# catch. The specific type is `divide_by_zero`; its parent `math_failure` is
# there for a handler that does not care which kind it was.
fn describe f:divide_by_zero
"no divisor: {f}"
fn describe f:math_failure
"some math failure: {f}"
fn describe n
"result: {n}"
fn safe_ratio a b
a / b
good = safe_ratio 10 2
bad = safe_ratio 10 0
print (describe good) .> (_ -> print (describe bad))
result: 5
no divisor: division by zero
fn fact 0
1
fn fact n
n * fact (n - 1)
answer = fact 20
print "20! = {answer}"
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.
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)
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 _.
print "one" .> (_ -> print "two") .> (_ -> print "three")
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.
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))
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.
type money
cents
fn to_string (money cents)
"${cents / 100}.{cents - cents / 100 * 100}"
price = money 350
print "that costs {price}"
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.
# 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.
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.
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.
jq -S on the 188 KB benchmark document, 1.7× jq on path extraction (25/25 interleaved heats)kanso repl) with it0-history, in your terminal and on the playgroundclone, build, run. one binary carries the whole toolchain—interpreter, native compiler, test runner, package manager—and chapter 01 of the book starts here.
$ 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.