appendix c

canonical form

most languages ship a formatter—a second program that rewrites your source into the house style. kanso ships none, because it would have nothing to do. canonical form is not a convention layered on top of the grammar; it is the grammar. a program has exactly one legal rendering, and every deviation is a compile error with a caret under it. this appendix is the reference for that law: each rule stated plainly, the reason it exists, and the diagnostic you get when you break it. every broken sample here was run through kanso check; every error text is copied from the machine, not typed from memory.

the founding principle is one sentence: anything a style guide, linter, or code review would enforce by convention, kanso enforces by making the alternative a compile error. the payoff is concrete. there is no style debate, because there is no style to debate. there is no formatter to configure, run, or argue about. and every diff you will ever review is purely semantic—no reflowed lines, no whitespace churn, no "nit: spacing" comments. the source contains only decisions.

here is a whole, legal file. read it, then spend the rest of the appendix learning why each character sits where it does:

ordered.kso
type circle
  r

fn area (circle r)
  r * r * 3.14159

print "area of r=2: {area (circle 2.0)}"
kanso run ordered.kso
area of r=2: 12.56636

whitespace, two at a time

indentation is spaces, and the only legal depths are zero and two-per-level. a body indents its lines by two more spaces than the header that owns them. three spaces, four spaces, one—all rejected, with the exact count named back to you:

indent_check.kso
 x = 1
 print "{x}"
kanso check indent_check.kso
error[formatting]: indentation must be 0 or 2 spaces, found 1
  --> indent_check.kso:1:1
   1 |  x = 1
       ^
error[formatting]: indentation must be 0 or 2 spaces, found 1
  --> indent_check.kso:2:1
   2 |  print "{x}"
       ^

a tab is not part of the grammar at all. the width of a tab depends on whose editor renders it, and a language with one rendering per program cannot admit a character whose meaning is a display setting:

tabs_check.kso
	print "hi"
kanso check tabs_check.kso
error[formatting]: tabs are not part of the canonical grammar; indent with spaces
  --> tabs_check.kso:1:1
   1 | 	print "hi"
       ^

within a line, spacing is fixed too. exactly one space sits on each side of =, and one space separates a call from its argument. no more, no fewer, and none glued away entirely. cram a binding and the compiler points at both the missing space before and the missing space after:

spacing_check.kso
answer=42
print "{answer}"
kanso check spacing_check.kso
error[formatting]: canonical form requires exactly one space here
  --> spacing_check.kso:1:7
   1 | answer=42
             ^
error[formatting]: canonical form requires exactly one space here
  --> spacing_check.kso:1:8
   1 | answer=42
              ^

and space you cannot even see is still a decision the file records, so it is still forbidden. trailing whitespace at the end of a line—the classic invisible diff—has no meaning and no place:

trailing_check.kso
print "hi" 
kanso check trailing_check.kso
error[formatting]: trailing whitespace is not part of the canonical grammar
  --> trailing_check.kso:1:11
   1 | print "hi" 
                 ^

no commas, no call parentheses

application is juxtaposition. f a b is a call to f with two arguments, and the separator is a single space. there are no commas—not in argument lists, not in list literals, not in patterns. bring the comma from another language and kanso names the habit:

commas_check.kso
total = sum [1, 2]
print "{total}"
kanso check commas_check.kso
error[formatting]: kanso has no commas; enumerations are space-separated
  --> commas_check.kso:1:15
   1 | total = sum [1, 2]
                     ^

likewise there are no parentheses wrapping a call's arguments. parentheses group sub-expressions—f (g x) y—and do nothing else, so print("hi") reads as print applied to nothing:

call_paren_check.kso
print("hi")
kanso check call_paren_check.kso
error[formatting]: application is a space, so `print x` calls `print` — `print(x)` reads as `print` applied to nothing, then a parenthesised `x`
  --> call_paren_check.kso:1:6
   1 | print("hi")
            ^

one order per file

wherever order carries no meaning, canonical order is mandatory, so that two programs differing only in shuffle are impossible: imports, the members of a typeset, the fields of a keyed read. declaration order is not one of those places—it carries narrative, and it stays yours. what the file does fix is the two groups: type declarations come first, then functions and constants. put a type below a function and the compiler tells you which one to move and where:

order_types_check.kso
fn use_it t
  t

type thing
  size

pub play = print "x"
kanso check order_types_check.kso
error[formatting]: canonical order places type declarations before functions; move `thing` up
  --> order_types_check.kso:4:6
   4 | type thing
            ^

and it reaches into a keyed destructuring read, where you name a subset of fields to pull out. the names you list are a set, so they are alphabetized like any other:

keyed_order_check.kso
type user
  admin
  name

pub play =
  { name admin } = user false "clay"
  print "{name} {admin}"
kanso check keyed_order_check.kso
error[formatting]: keyed reads list fields in alphabetical order: `admin` before `name`
  --> keyed_order_check.kso:6:10
   6 |   { name admin } = user false "clay"
                ^

blank lines carry meaning

a blank line is punctuation, not decoration, so its use is exact. top-level declarations are separated by exactly one blank line—not zero, not two. run two declarations together and the compiler asks for the separator:

blank_between_check.kso
fn greet _
  print "hi"
fn wave _
  print "yo"

pub play = greet 0 . wave
kanso check blank_between_check.kso
error[formatting]: exactly one blank line separates top-level declarations
  --> blank_between_check.kso:3:1
   3 | fn wave _
       ^

inside a body, blank lines are banned entirely. a body is one uninterrupted run of statements; the moment you want to group some of them apart with a blank line, you are describing structure the grammar already has words for—>> walls and bindings—and it wants those words, not a gap:

blank_body_check.kso
fn greet _
  x = 1

  print "{x}"

pub play = greet 0
kanso check blank_body_check.kso
error[formatting]: blank lines may not appear inside a body
  --> blank_body_check.kso:4:1
   4 |   print "{x}"
       ^

eighty columns, and the continuation line

a line holds at most eighty characters. the diagnostic even counts them for you:

width_check.kso
print "this string pads the line out well past the canonical width limit of eighty"
kanso check width_check.kso
error[formatting]: a line holds at most 80 characters — this one has 83
  --> width_check.kso:1:81
   1 | print "this string pads the line out well past the canonical width limit of eighty"
                                                                                       ^

width is rendering only—it never changes how many statements a body has. an over-wide statement stays one statement, wrapped across continuation lines. there are exactly two continuation forms, and they are the same two tokens that express ordering everywhere else: . for a data-flow pipe, and >> for a pure sequence. each step lands on its own line, indented two past the statement, one step per line.

here is a sequence too wide for one line. its first step stays on the main = line; each further >> step drops to a continuation line at indent plus two:

wrap_wall.kso
print "steeping the sencha in the small clay pot for exactly ninety seconds"
  >> print "pouring the water in a thin stream from well above the first cup"
kanso run wrap_wall.kso
steeping the sencha in the small clay pot for exactly ninety seconds
pouring the water in a thin stream from well above the first cup

and here is a pipe too wide for one line, wrapped onto . continuation lines. the whole thing is still a single binding of ranked:

wrap_pipe.kso
import "std/list"

ranked = [40 15 8 23 42 16 4 108 71 55 90 33 27 61 19 77 88 100 200]
  . list/sort
  . list/sum
print "sum of the sorted roster: {ranked}"
kanso run wrap_pipe.kso
sum of the sorted roster: 1077

because there is one rendering, the wrap is not optional. if a statement fits on one line, it must be on one line—wrapping something that fits is "needless continuation," and the compiler tells you the width it would occupy unwrapped:

needless_wrap_check.kso
total = [9 1 8 2 7] . sort
  . sum
print "sum: {total}"
kanso check needless_wrap_check.kso
error[formatting]: needless continuation: this statement fits on one line (32 characters)
  --> needless_wrap_check.kso:2:3
   2 |   . sum
         ^

and the wrap is all-or-nothing. once a statement wraps, every step gets its own continuation line—you cannot leave two steps sharing the first line and drop the rest. partial chaining has no canonical form, so it has none at all:

partial_chain_check.kso
print "steeping the finest uji sencha" >> print "pouring the hot water"
  >> print "serving it in the warmed cups with seasonal wagashi"
kanso check partial_chain_check.kso
error[formatting]: no partial chaining: a chain fits on one line, or each step gets its own `>>` continuation line
  --> partial_chain_check.kso:1:40
   1 | print "steeping the finest uji sencha" >> print "pouring the hot water"
                                              ^

names: snake_case, lowercase, always

identifiers are lowercase snake_case, with no exceptions and no second convention to remember. no camelCase, no PascalCase, no SCREAMING_CONSTANTS. a single naming scheme means a name looks identical everywhere it appears, and a reader never wonders whether case carries meaning—it does not:

case_check.kso
myName = 1
print "{myName}"
kanso check case_check.kso
error[formatting]: identifiers are snake_case, all lowercase, always
  --> case_check.kso:1:3
   1 | myName = 1
         ^
error[formatting]: identifiers are snake_case, all lowercase, always
  --> case_check.kso:2:11
   2 | print "{myName}"
                 ^

one convention you may have expected is absent: there is no leading-underscore rule for privacy. a top-level name is module-private by default, and the leading keyword pub marks the ones that are api surface—visibility runs on that keyword, not on how you spell the name. the underscore _ means exactly one thing, a discard in a pattern, and nothing about it says "private."

strings are double-quoted

string literals use double quotes. the lexer doesn't recognize a single quote as a string delimiter at all, so it just names the character it choked on:

quote_check.kso
print 'hi'
kanso check quote_check.kso
error[syntax]: unexpected character `'`
  --> quote_check.kso:1:7
   1 | print 'hi'
             ^

inside a double-quoted string, {braces} interpolate, and the braces take any expression—a name, arithmetic, a call. you have seen this throughout the appendix: "sum of the sorted roster: {ranked}", "area of r=2: {area (circle 2.0)}". there is one string syntax, one interpolation syntax, and no format-string mini-language bolted on beside it.

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

the whole appendix reduces to one habit: type the mistake once, read the caret, learn the rule for good. two-space indents, one space around =, alphabetical imports, nothing trailing, eighty columns. it isn't a style i prefer; it's the only rendering that parses. i sleep well because there is nothing left to decide.

that is the whole law. you can now read any diagnostic in the error[formatting] family and know it names a rule with exactly one legal answer, and you can write a file that kanso check passes on the first try: two-space indents, one space around every = and operator, no commas and no call parentheses, types before functions and both alphabetical, one blank line between declarations and none inside a body, eighty columns with . and >> continuation lines when a statement runs long, snake_case names, and double-quoted strings. nothing here is a preference. it is the grammar, and the grammar is the reason every kanso diff you read contains only decisions.

exercises

  1. take wrap_pipe.kso and remove enough elements from the list that the ranked binding would fit on one line unwrapped. predict which diagnostic kanso check produces before you run it, then confirm.
  2. write a file with a type named widget, a function named build, and main—deliberately in the wrong order. run kanso check and note how many separate ordering errors you can provoke, and in what sequence you must fix them to reach ok.
  3. a body has a binding you want to set visually apart from the effects below it. you reach for a blank line and the compiler rejects it. what does the grammar offer instead, and what does that say about when a blank line would ever have been the right tool?
  4. construct a single statement that is exactly 80 characters and one that is exactly 81. verify the boundary is where the diagnostic claims it is, and read the character count it reports back.