Half an hour, top to bottom. Every snippet below is a real program and every output is what it actually prints — the suite checks them.
A file is a program: top-level statements run in order, and there is no ceremony to get through first.
println("hello, world")
val who = "Ada"
println("hello ${who}, ${1 + 2} things")hello, world hello Ada, 3 things
val binds once, var may be reassigned. Numbers copy; lists and maps are shared. There are no implicit numeric conversions.
val name = "Ada"
var count = 0
count += 1
val n = 3
val good = n.toFloat() / 2.0
val ratio: Float = 1 / 2 // a literal adapts
val xs = [1, 2]
val ys = xs
ys.add(3)
println("${good} ${ratio} ${xs}")1.5 0.5 [1, 2, 3]
Which word you use says whether there is a result. A func must declare what it returns; a proc cannot — so Unit is never written by hand.
func add(a: Int, b: Int): Int { a + b }
proc greet(name: String, greeting: String = "hello") {
println("${greeting}, ${name}!")
}
println(add(2, 3))
greet("Ada")
greet("Ada", greeting = "hi")5 hello, Ada! hi, Ada!
Braces are mandatory and a block's value is its last expression, which is why if produces one. unless (c) is if (not c).
val n = -2
val sign = if (n < 0) { "neg" } else { "pos" }
func lengthOf(s: String?): Int {
unless (s != null) { return 0 }
return s.length
}
for (i in 0..3) { println(i) }
println("${sign} ${lengthOf(null)} ${lengthOf("abcd")}")0 1 2 neg 0 4
One construct covers what other languages split between switch and match: no fall-through, first arm wins, and it is an expression.
func describe(n: Int): String {
return when (n) {
0 -> "zero"
1, 2, 3 -> "small"
in 4..10 -> "medium"
else -> "large"
}
}
println(describe(2))
println(describe(7))
println(describe(99))small medium large
A type does not admit null unless you write ?. After a check that proves something about an immutable binding, the fact holds — and Keal carries it further than most.
var maybe: String? = null
println(maybe?.length)
println(maybe ?: "default")
val s: String? = "abc"
if (s != null) { println(s.length) }
println(s != null and s.length > 0)
println(s != null implies s.length > 0)null default 3 true true
Lists and maps are built in, with the higher-order methods you expect, typed generically.
val xs = [1, 2, 3, 4]
println(xs.map({ it * 2 }))
println(xs.filter({ it % 2 == 0 }))
println(xs.fold(0, { acc, x -> acc + x }))
val ages = {"ada": 36, "alan": 41}
for (name in ages) { println("${name} is ${ages[name]!!}") }[2, 4, 6, 8] [2, 4] 10 ada is 36 alan is 41
A record is the data case: immutable fields, structural equality, destructuring. A class is the one that can change.
record Point(val x: Float, val y: Float)
val a = Point(1.0, 2.0)
val b = Point(1.0, 2.0)
println(a == b)
val Point(x, y) = a
println("${x} ${y}")
class Counter(var n: Int) {
proc bump() { this.n += 1 }
}
val c = Counter(0)
c.bump()
println(c.n)true 1.0 2.0 1
Generics are monomorphised — no erasure, no boxing. A trait is a capability a type parameter can be required to have, not a type of its own.
func firstOr<T>(xs: List<T>, fallback: T): T {
for (x in xs) { return x }
return fallback
}
println(firstOr([1, 2], 0))
println(firstOr(["a"], "z"))
func total<T: Add>(xs: List<T>, zero: T): T {
var acc = zero
for (x in xs) { acc = acc + x }
return acc
}
println(total([1, 2, 3], 0))1 a 6
Written as words, at one flat precedence, so a mixed expression must say what it means with parentheses.
val a = true val b = false println(a and b) println(a or b) println(a xor b) println(a nand b) println(a nor b) println(a xnor b) println(a implies b) println(not a)
false true true true false false false false
An Int is 64 bits, and seven operators read it as those. Words, because and, or and xor already belong to Bool. They mix with nothing without parentheses — but they bind tighter than comparison, so the test everybody writes needs none.
val argb = (255 shl 24) bor (16 shl 16) bor (32 shl 8) bor 64 println((argb ushr 16) band 0xFF) println(argb band 0xFF) println(0xF0 bxor 0xFF) println(bnot 0) val flag = 0x22 println(flag band 2 != 0)
16 64 15 -1 true
deinit runs when the last reference dies, at the next statement boundary. weak writes the back edge of a cycle without holding it alive, so the cycle still dies.
var freed = 0
class Item(val id: Int) {
weak var owner: Owner? = null
proc deinit() { freed += 1 }
}
class Owner(val id: Int) {
var held: Item? = null
proc deinit() { freed += 1 }
}
proc pair() {
val o = Owner(1)
val it = Item(2)
o.held = it
it.owner = o
}
pair()
println("freed ${freed}")freed 2
keal build compiles through C11 to a real executable, and what it cannot compile it refuses by name — it never mis-compiles.
native """
#include <math.h>
static double keal_hypot(double a, double b) { return hypot(a, b); }
"""
extern func hypot(a: Float, b: Float): Float = "keal_hypot"
println(hypot(3.0, 4.0))5.0
A promise about when the work happens: the compiler runs it and writes the answer into the program as a literal. Where it cannot, it refuses by name rather than quietly leaving the work for run time — and it always finishes, because a compiler that never answers is not a tool.
constexpr func squares(n: Int): List<Int> {
var out: List<Int> = []
for (i in 1..n) { out.add(i * i) }
return out
}
constexpr val KB = 1024
constexpr val TABLE: List<Int> = squares(8)
println("${KB * KB} ${TABLE.size} ${TABLE[6]}")1048576 7 49
A closed set of names. The checker knows every value the type has, so a when over one needs no else — and the day somebody adds a variant, every when that forgot it is an error rather than a surprise at run time.
enum Suit { Hearts, Diamonds, Clubs, Spades }
func isRed(s: Suit): Bool {
return when (s) {
Suit.Hearts, Suit.Diamonds -> true
Suit.Clubs, Suit.Spades -> false
}
}
println("${Suit.Hearts} ${isRed(Suit.Hearts)} ${isRed(Suit.Spades)}")Hearts true false
A named piece of syntax, spliced where it is written. The ! is not decoration: a macro may assign to what it was given, run an argument twice or never, and let a return pass through to the function around it — three things a call cannot do.
macro swap(a, b) {
val held = a
a = b
b = held
}
macro guard(cond, fallback) {
unless (cond) { return fallback }
}
func describe(n: Int): String {
guard!(n > 0, "not positive")
return "ok"
}
var p = 1
var q = 2
swap!(p, q)
println("${p} ${q} ${describe(-3)} ${describe(7)}")2 1 not positive ok