a tour of kanso
each lesson below is a program and what it printed. the programs run in your browser: press run in a panel's title to run it, edit it and run it again, and double-click the title to put it back. nothing is installed. each lesson takes a minute and links to the chapter of the book that covers the idea in full.
the tour shows the language. the book explains it, and chapter 13 onward builds and ships real programs.
01 · hello
a kanso program is a file of statements. print takes one argument, written after it with a space: there are no parentheses around arguments and no semicolons. press run on any panel on this page, change the program, and press it again.
print "hello, world"
hello, world
more in chapter 01.
02 · names and values
= binds a name to a value. text in double quotes interpolates any expression between braces.
name = "mugi"
cups = 3
print "{name} has had {cups} cups, and wants {cups + 1}"
mugi has had 3 cups, and wants 4
more in chapter 02.
03 · numbers
an int has no ceiling, so the product prints all twenty-two digits. dividing two ints keeps whole numbers; once a float is involved, the answer is a float.
big = 1000000000 * 1000000000 * 1000
half = 7 / 2
widened = 7 / 2.0
print "{big}" .> (_ -> print "{half} and {widened}")
1000000000000000000000
3 and 3.5
more in chapter 02.
04 · lists
a list is written in brackets, with spaces between the elements and no commas. indexing starts at 1. the list functions live in std/list: list/map answers a recipe, and list/to_list turns it into a list.
import "std/list"
teas = ["sencha" "hojicha" "gyokuro"]
loud = list/to_list (list/map teas (t -> "{t}!"))
print "first: {teas[1]}" .> (_ -> print "{loud}")
first: sencha
["sencha!" "hojicha!" "gyokuro!"]
more in chapter 12.
05 · maps
a map pairs keys with values. put answers a new map; the old one is unchanged, because no value in kanso is ever changed in place. entries lists the pairs in key order.
prices = { "dango":350 "taiyaki":500 }
prices = put prices "mochi" 300
print "{prices["mochi"]} yen" .> (_ -> print "{entries prices}")
300 yen
[entry "dango" 350 entry "mochi" 300 entry "taiyaki" 500]
more in chapter 02.
06 · records
a type lists named fields, and its name builds one, with the fields in the order they were declared. a binding pattern takes a record apart; a dot reads one field.
type tea
name
steep
t = tea "sencha" 60
tea name seconds = t
print "{name}, {seconds} seconds" .> (_ -> print "or just {t.name}")
sencha, 60 seconds
or just sencha
more in chapter 03.
07 · functions
fn declares a function, and its body is indented two spaces. the last line is the answer. a call is the function name followed by its arguments, and parentheses group a call that is itself an argument.
fn steep_for name seconds
"steep {name} for {seconds} seconds"
print (steep_for "hojicha" 30)
steep hojicha for 30 seconds
more in chapter 03.
08 · overloads
a function can have several arms, and each call goes to the most specific one that fits. the literal 0 beats the plain n, so the base case of a recursion is an arm, with no if.
fn fact 0
1
fn fact n
n * fact (n - 1)
print "20! = {fact 20}"
20! = 2432902008176640000
more in chapter 03.
09 · if
if is a function of three arguments: a condition and two answers. use it for a question about one value's content. a question about what kind of value something is gets an arm instead.
fn ticket age
if (age < 13) "child" "adult"
print (ticket 9) .> (_ -> print (ticket 40))
child
adult
more in chapter 03.
10 · kinds of a thing
a type with no fields is a marker: its name is its only value. a typeset names a closed set of types. an arm for each member does the work an enum and a switch do elsewhere.
type cold
type hot
type warm
type temperature cold hot warm
fn serve cold
"over ice"
fn serve hot
"in a warmed cup"
fn serve warm
"as it comes"
print (serve hot) .> (_ -> print (serve cold))
in a warmed cup
over ice
more in chapter 03.
11 · nothing there
looking up a key that is not there answers none, an ordinary value. an arm takes it like any other.
fn describe none
"not on the menu"
fn describe price
"{price} yen"
menu = { "dango":350 }
print (describe menu["dango"]) .> (_ -> print (describe menu["pocky"]))
350 yen
not on the menu
more in chapter 04.
12 · failure
err makes a failure. a failure passes every function it reaches without running it, until it reaches the top of the program, which reports it with where it was born. .> runs its step on a value and steps past a failure.
fn share _ 0
err "no one to share with"
fn share yen people
yen / people
fn with_tip yen
yen + yen / 10
print "{with_tip (share 1200 3)}"
.> (_ -> share 1200 0 .> with_tip .> (y -> print "{y}"))
440
error[endpoint]: unhandled err reached the executor: "no one to share with"
born in share at failure.kso:2
more in chapter 04.
13 · effects
print does not print. it answers a description of printing, so a description can be named and used twice. .> puts one description after another, and the runtime performs the whole thing once the program has been evaluated.
knock = print "kon kon"
knock .> (_ -> knock) .> (_ -> print "come in")
kon kon
kon kon
come in
more in chapter 05.
14 · pipes
x . f is f x, so a chain reads left to right in the order things happen.
fn shout s
"{s}!"
fn twice s
"{s} {s}"
"irasshaimase" . shout . twice . print
irasshaimase! irasshaimase!
more in chapter 05.
15 · functions as values
(p -> p < 400) is a function written inline. &add_tax 10 supplies the first argument and waits for the rest.
import "std/list"
fn add_tax rate yen
yen + yen * rate / 100
with_tax = &add_tax 10
prices = [350 500 300]
print "{list/to_list (list/map prices with_tax)}"
.> (_ -> print "{list/to_list (list/select prices (p -> p < 400))}")
[385 550 330]
[350 300]
more in chapter 05.
16 · sequences
list/naturals counts forever, and nothing runs until something consumes it. list/take and list/find decide how much of it is computed.
import "std/list"
squares = list/map list/naturals (n -> n * n)
print "{list/to_list (list/take squares 5)}"
.> (_ -> print "{list/find squares (s -> s > 50)}")
[1 4 9 16 25]
64
more in chapter 12.
17 · at the same time
two statements with nothing between them run together, as green threads. a sleep parks one while the other works, and the order of what prints follows the numbers in the program, the same on every run.
import "std/time"
kettle = print "kettle: on"
.> (_ -> time/sleep 30)
.> (_ -> print "kettle: boiled")
cups = print "cups: warming"
.> (_ -> time/sleep 10)
.> (_ -> print "cups: warm")
kettle
cups
kettle: on
cups: warming
cups: warm
kettle: boiled
more in chapter 06.
18 · tests
a test is a constant whose name starts with test_ and whose value is a boolean. kanso test evaluates each one. this panel shows what the runner printed; tests run from a terminal rather than the page.
fn steep_for name seconds
"steep {name} for {seconds} seconds"
test_steep = steep_for "sencha" 60 == "steep sencha for 60 seconds"
test_wrong_on_purpose = steep_for "sencha" 60 == "steep it"
test_steep ... ok
test_wrong_on_purpose ... FAILED (returned false)
1 passed, 1 failed
more in chapter 07, and std/expect for tests that say what they saw.
where to go next
to run kanso on your own machine, chapter 01 builds the toolchain. to learn the language properly, read the book from there in order. to see where it goes, chapter 13 builds a command-line tool from an empty directory, chapter 14 a web service, and chapter 15 measures one and ships both. the playground has a bigger editor and a repl.