簡素
the language where the source contains only decisions.
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 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.
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.
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; fields, imports, 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: 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.
every sample below is verbatim from examples/ in the repository.
safe_ratio 10 0 returns an err—division by zero is a value, not a panic. it flows through untouched until describe dispatches on it: one overload for err reason, one for the bare number. no wrapper to unwrap, no exception to catch.
fn describe (err reason)
"failed: {reason}"
fn describe n
"result: {n}"
main =
good = safe_ratio 10 2
bad = safe_ratio 10 0
print (describe good) >> print (describe bad)
fn safe_ratio a b
a / b
result: 5
failed: division by zero
fn fact 0
1
fn fact n
n * fact (n - 1)
main =
answer = fact 20
print "20! = {answer}"
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.
type user
admin:bool
name:string
main =
clay = user true "clay"
print (welcome clay)
fn welcome (user _ name)
"irasshaimase, {name}"
irasshaimase, clay
every type is a single-constructor record; fields are alphabetical, enforced. construction is positional and destructuring is how you take records apart.
main = print "one" >> print "two" >> print "three"
one
two
three
>> sequences descriptions without passing data. together with ., it 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}"
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.
type money
cents:int
pub play =
price = money 350
print "that costs {price}"
fn to_string (money cents)
"${cents / 100}.{cents - (cents / 100 * 100)}"
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—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.
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.
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.