Hola, mundo
El punto de entrada y el efecto de salida estándar, con handler por defecto.
fn main() {
println("Hello, kaikai")
}$ kai run 01_hello.kaiHello, kaikai Quickstart
Los programas del quickstart, en orden de lectura. Cada uno cabe en una pantalla y muestra una idea del lenguaje.
El punto de entrada y el efecto de salida estándar, con handler por defecto.
fn main() {
println("Hello, kaikai")
}$ kai run 01_hello.kaiHello, kaikai Tipos suma, guardas en el match y un pipeline sobre un literal de rango.
type Tag
= Ambos
| Fizz
| Buzz
| Otro(Int)
# Las guardas permiten que cada rama lleve su propia condición, así la
# cascada de `if / else if` se colapsa dentro del match.
fn clasificar(n: Int) : Tag = match n {
n if n % 15 == 0 -> Ambos
n if n % 3 == 0 -> Fizz
n if n % 5 == 0 -> Buzz
n -> Otro(n)
}
fn etiqueta(c: Tag) : String = match c {
Ambos -> "FizzBuzz"
Fizz -> "Fizz"
Buzz -> "Buzz"
# Interpolación de strings: `#{expr}` renderiza cualquier valor
# mostrable.
Otro(n) -> "#{n}"
}
fn main() {
[1..15]
| clasificar # map: [Int] -> [Tag]
| etiqueta # map: [Tag] -> [String]
|> list.foreach(println) # apply: imprime cada línea
}$ kai run 02_fizzbuzz.kai1
2
Fizz
4
Buzz
...
FizzBuzz Un tipo suma recursivo como AST, recorrido con calce de patrones.
type Expr
= Lit(Int)
| Suma(Expr, Expr)
| Mult(Expr, Expr)
| Neg(Expr)
# Una función cuyo cuerpo entero es una sola expresión usa `=`: sin
# llaves y sin `return`.
fn eval(e: Expr) : Int = match e {
Lit(n) -> n
Suma(l, r) -> eval(l) + eval(r)
Mult(l, r) -> eval(l) * eval(r)
Neg(x) -> -eval(x)
}
fn mostrar(e: Expr) : String = match e {
Lit(n) -> "#{n}"
Suma(l, r) -> "#{mostrar(l)} + #{mostrar(r)}"
Mult(l, r) -> "#{mostrar(l)} * #{mostrar(r)}"
Neg(x) -> "-#{mostrar(x)}"
}
fn main() {
let e = Suma(Lit(2), Mult(Lit(3), Lit(4)))
println("#{mostrar(e)} = #{eval(e)}")
}$ kai run 03_calculator.kai2 + 3 * 4 = 14 Un efecto propio: la función declara qué usa, quien la llama decide cómo se cumple.
effect Log {
log(msg: String) : Unit
}
fn saludar(nombre: String) : Unit / Log {
# `#{...}` interpola dentro del string — sin concatenación manual.
Log.log("hello, #{nombre}")
}
fn main() {
handle {
saludar("kaikai")
saludar("world")
} with Log {
log(msg, resume) -> {
println("[INFO] #{msg}")
resume(())
}
}
}$ kai run 04_effect.kai[INFO] hello, kaikai
[INFO] hello, world Dos fibras cooperativas que ceden el control en puntos explícitos.
import spawn
import loop
fn trabajador(tag: String, n: Int) : Unit / Stdout + Spawn {
loop.repeat(n) {
println(tag)
spawn.yield()
}
}
fn main() {
# Forma con lambda final: `spawn.spawn { ... }` en vez de
# `spawn.spawn(() => ...)`.
let f = spawn.spawn { trabajador("B", 3) }
trabajador("A", 3)
spawn.await(f)
}$ KAI_THREADS=1 kai run 05_concurrent.kaiA
B
A
B
A
B Cuatro operadores para cuatro intenciones: aplicar, mapear, aplanar y filtrar.
fn cuadrado(n: Int) : Int = n * n
fn divisores(n: Int) : [Int] = [1, n]
fn es_par(n: Int) : Bool = n % 2 == 0
fn main() {
# `[1..4]` es un literal de rango — sin acumulador escrito a mano.
# `[1..10..2]` agrega un paso.
let total = [1..4] # [1, 2, 3, 4]
| cuadrado # [1, 4, 9, 16] (map)
|| divisores # [1, 1, 1, 4, 1, 9, 1, 16] (flat-map)
|? es_par # [4, 16] (filter)
|> list.sum # 20 (apply)
println("total=#{total}")
}$ kai run 06_pipes.kaitotal=20 Las unidades viven en el tipo, así que el compilador no deja mezclar monedas.
unit USD
unit EUR
fn convertir_a_usd(monto: Real<EUR>, tasa: Real<USD/EUR>) : Real<USD>
= monto * tasa
fn main() {
let salario : Real<USD> = 1000.0<USD>
let mercado : Real<USD> = 250.0<USD>
let comision : Real<USD> = 5.0<USD>
let reembolso : Real<EUR> = 91.0<EUR>
let tasa : Real<USD/EUR> = 1.10<USD/EUR>
let balance = salario - mercado - comision + convertir_a_usd(reembolso, tasa)
# 1000 - 250 - 5 + 100.1 = 845.1 USD
println("balance=#{balance}")
}$ kai run 07_uom.kaibalance=845.1 USD Por qué las unidades no son un caso especial, y cómo declarar un kind propio.
unit m
# `Measure` está declarado sobre `AbelianGroup`, que tiene clausura
# multiplicativa: metros por metros es una cantidad real, así que
# `u^2` es una unidad que puedes nombrar. `[u: Measure]` es un
# parámetro de tipo común que recorre unidades en vez de tipos.
fn area[u: Measure](w: Real<u>, h: Real<u>) : Real<u^2> = w * h
# Como las unidades son solo un kind, la maquinaria queda abierta:
# declara el tuyo. Los puntos de experiencia se suman, pero `xp^2` no
# significa nada, así que este kind se declara sobre `Module`: un
# grupo aditivo SIN clausura multiplicativa. Una cantidad es escalar o
# lleva exactamente un habitante, y `fn sq(a: Real<xp>) : Real<xp^2>`
# se rechaza justo donde se escribe la unidad:
#
# error: unit `xp^2` does not exist: `Points` habitants have no
# products or powers
#
# Misma maquinaria que `Measure`, otra teoría, otra álgebra.
kind Points : Module with points
points xp
fn ganar(a: Real<xp>, b: Real<xp>) : Real<xp> = a + b
fn main() {
let piso = area(3.0<m>, 4.0<m>)
let puntaje = ganar(120.0<xp>, 45.0<xp>)
# Habitantes de kinds distintos tampoco unifican nunca: `3.0<m> +
# 4.0<xp>` es un error de tipo. Tu kind no puede filtrarse en el de
# nadie más.
println("piso=#{piso}")
println("puntaje=#{puntaje}")
}$ kai run 09_kinds.kaipiso=12 m^2
puntaje=165 xp Precondiciones y postcondiciones que viven en la firma de la función.
fn dividir(a: Int, b: Int) : Int
requires b != 0
ensures result * b == a
{
a / b
}
# Valor absoluto: la postcondición garantiza un resultado no negativo
# y de la misma magnitud que el argumento.
fn abs(n: Int) : Int
ensures result >= 0
ensures result == n or result == -n
{
if n >= 0 { n } else { -n }
}
fn main() {
println("#{dividir(10, 2)}") # 5
println("#{abs(-3)}") # 3
}$ kai run 08_contracts.kai5
3