kaikai
  • Get started
  • Examples
  • The name
  • The book
  • Ecosystem
  • Community
ES

Quickstart

Examples

The quickstart programs, in reading order. Each fits on a screen and shows one idea from the language.

01 Hello, world 02 FizzBuzz 03 Calculator 04 Effects 05 Concurrency 06 Pipes 07 Units of measure 08 Kinds 09 Contracts
01

Hello, world

The entry point and the default-handled stdout effect.

01_hello.kai
fn main() {
  println("Hello, kaikai")
}
Output
$ kai run 01_hello.kaiHello, kaikai
02

FizzBuzz

Sum types, match guards, and a pipeline over a range literal.

02_fizzbuzz.kai
type Tag
  = Both
  | Fizz
  | Buzz
  | Other(Int)

# Guards let every arm carry its own condition, so the cascade of
# `if / else if` collapses into the match itself.
fn classify(n: Int) : Tag = match n {
  n if n % 15 == 0 -> Both
  n if n % 3 == 0  -> Fizz
  n if n % 5 == 0  -> Buzz
  n                -> Other(n)
}

fn label(c: Tag) : String = match c {
  Both     -> "FizzBuzz"
  Fizz     -> "Fizz"
  Buzz     -> "Buzz"
  # String interpolation: `#{expr}` renders any displayable value.
  Other(n) -> "#{n}"
}

fn main() {
  [1..15]
    | classify                # map: [Int] -> [Tag]
    | label                   # map: [Tag] -> [String]
    |> list.foreach(println)  # apply: print each line
}
Output
$ kai run 02_fizzbuzz.kai1
2
Fizz
4
Buzz
...
FizzBuzz
03

Calculator

A recursive sum type as an AST, walked by pattern matching.

03_calculator.kai
type Expr
  = Lit(Int)
  | Add(Expr, Expr)
  | Mul(Expr, Expr)
  | Neg(Expr)

# A function whose whole body is one expression uses `=`; no braces,
# no `return`.
fn eval(e: Expr) : Int = match e {
  Lit(n)    -> n
  Add(l, r) -> eval(l) + eval(r)
  Mul(l, r) -> eval(l) * eval(r)
  Neg(x)    -> -eval(x)
}

fn show(e: Expr) : String = match e {
  Lit(n)    -> "#{n}"
  Add(l, r) -> "#{show(l)} + #{show(r)}"
  Mul(l, r) -> "#{show(l)} * #{show(r)}"
  Neg(x)    -> "-#{show(x)}"
}

fn main() {
  let e = Add(Lit(2), Mul(Lit(3), Lit(4)))
  println("#{show(e)} = #{eval(e)}")
}
Output
$ kai run 03_calculator.kai2 + 3 * 4 = 14
04

Effects

A custom effect: the function declares what it uses, the caller decides how it is met.

04_effect.kai
effect Log {
  log(msg: String) : Unit
}

fn greet(name: String) : Unit / Log {
  # `#{...}` interpolates into the string — no manual concatenation.
  Log.log("hello, #{name}")
}

fn main() {
  handle {
    greet("kaikai")
    greet("world")
  } with Log {
    log(msg, resume) -> {
      println("[INFO] #{msg}")
      resume(())
    }
  }
}
Output
$ kai run 04_effect.kai[INFO] hello, kaikai
[INFO] hello, world
05

Concurrency

Two cooperative fibers yielding control at explicit points.

05_concurrent.kai
import spawn
import loop

fn worker(tag: String, n: Int) : Unit / Stdout + Spawn {
  loop.repeat(n) {
    println(tag)
    spawn.yield()
  }
}

fn main() {
  # Trailing-lambda form: `spawn.spawn { ... }` instead of
  # `spawn.spawn(() => ...)`.
  let f = spawn.spawn { worker("B", 3) }
  worker("A", 3)
  spawn.await(f)
}
Output
$ KAI_THREADS=1 kai run 05_concurrent.kaiA
B
A
B
A
B
06

Pipes

Four operators for four intents: apply, map, flat-map and filter.

06_pipes.kai
fn square(n: Int) : Int = n * n

fn divisors(n: Int) : [Int] = [1, n]

fn is_even(n: Int) : Bool = n % 2 == 0

fn main() {
  # `[1..4]` is a range literal — no hand-written accumulator loop.
  # `[1..10..2]` adds a step.
  let total = [1..4]                  # [1, 2, 3, 4]
    | square                          # [1, 4, 9, 16]      (map)
    || divisors                       # [1, 1, 1, 4, 1, 9, 1, 16] (flat-map)
    |? is_even                        # [4, 16]            (filter)
    |> list.sum                       # 20                 (apply)

  println("total=#{total}")
}
Output
$ kai run 06_pipes.kaitotal=20
07

Units of measure

Units live in the type, so the compiler will not let you mix currencies.

07_uom.kai
unit USD
unit EUR

fn to_usd(amount: Real<EUR>, rate: Real<USD/EUR>) : Real<USD>
  = amount * rate

fn main() {
  let salary    : Real<USD> = 1000.0<USD>
  let groceries : Real<USD> =  250.0<USD>
  let fee       : Real<USD> =    5.0<USD>
  let refund    : Real<EUR> =   91.0<EUR>
  let rate      : Real<USD/EUR> = 1.10<USD/EUR>

  let balance = salary - groceries - fee + to_usd(refund, rate)

  # 1000 - 250 - 5 + 100.1 = 845.1 USD
  println("balance=#{balance}")
}
Output
$ kai run 07_uom.kaibalance=845.1 USD
08

Kinds

Why units are not a special case, and how to declare a kind of your own.

09_kinds.kai
unit m

# `Measure` is declared over `AbelianGroup`, which has multiplicative
# closure: metres times metres is a real quantity, so `u^2` is a unit
# you can name. `[u: Measure]` is an ordinary type parameter that
# ranges over units instead of over types.
fn area[u: Measure](w: Real<u>, h: Real<u>) : Real<u^2> = w * h

# Because units are only a kind, the machinery is open — declare your
# own. Experience points add up, but `xp^2` is meaningless, so this
# kind is declared over `Module`: an additive group with NO
# multiplicative closure. A quantity is either scalar or carries
# exactly one habitant, and `fn sq(a: Real<xp>) : Real<xp^2>` is
# rejected at the point the unit is written:
#
#   error: unit `xp^2` does not exist: `Points` habitants have no
#          products or powers
#
# Same machinery as `Measure`, different theory, different algebra.
kind Points : Module with points
points xp

fn gain(a: Real<xp>, b: Real<xp>) : Real<xp> = a + b

fn main() {
  let floor = area(3.0<m>, 4.0<m>)
  let score = gain(120.0<xp>, 45.0<xp>)

  # Habitants of different kinds never unify either: `3.0<m> +
  # 4.0<xp>` is a type error. Your kind cannot leak into anyone
  # else's.
  println("floor=#{floor}")
  println("score=#{score}")
}
Output
$ kai run 09_kinds.kaifloor=12 m^2
score=165 xp
09

Contracts

Preconditions and postconditions that live in the function signature.

08_contracts.kai
fn divide(a: Int, b: Int) : Int
  requires b != 0
  ensures  result * b == a
{
  a / b
}

# Absolute value: the postcondition guarantees a non-negative result
# of the same magnitude as the argument.
fn abs(n: Int) : Int
  ensures result >= 0
  ensures result == n or result == -n
{
  if n >= 0 { n } else { -n }
}

fn main() {
  println("#{divide(10, 2)}")   # 5
  println("#{abs(-3)}")         # 3
}
Output
$ kai run 08_contracts.kai5
3
kaikai

Programming language.

By Eduardo Díaz.

  • Get started
  • Examples
  • The name
  • The book
  • Ecosystem
  • Community
  • GitHub · kaikai
  • GitHub · kaikai-book
  • lnds.net