Lenguaje funcional con efectos algebraicos y fibras aisladas. Sin recolector de basura, sin borrow checker.

Diseñado para que humanos y agentes lo escriban juntos.

¿por qué este nombre?

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

fn greet(name: String) : Unit / Log {
  Log.log("hello, " ++ name)
}

fn main() {
  handle {
    greet("kaikai")
    greet("world")
  } with Log {
    log(msg, resume) -> {
      println("[INFO] " ++ msg)
      resume(())
    }
  }
}

Por qué kaikai

Efectos algebraicos

Efectos visibles en el tipo, handlers compositivos. Sin async/await que se propaga por toda la pila de llamadas.

Familia de pipes

Cuatro operadores, cuatro intenciones: |> aplica, | mapea, || aplana, |? filtra. Cada forma dice qué hace antes de leer la función.

Sin GC, sin borrow checker

Perceus reference counting + fibras aisladas. La memoria es por-fibra; no hay pausas globales.

Unidades y branded types

Real<USD>, String<UserId>, Int<Seconds>. Información en el tipo, costo cero en runtime.

Contratos y refinements

requires, ensures, Int where >= 0. Lo que hace SPARK, sin SMT solver.

Diálogo con el compilador

Holes (?), --holes-json, diagnósticos JSON. Diseñado para que humanos y agentes escriban juntos.

Ejemplos

Tres programas que muestran la forma del lenguaje. Todos se compilan con kai run.

01_hello.kai
# Hello, world.
#
# Every kaikai program starts at `fn main()`. `println` is the
# default-handled stdout effect — no `import` needed.
#
#   $ kai run examples/quickstart/01_hello.kai
#   Hello, kaikai

fn main() {
  println("Hello, kaikai")
}
02_fizzbuzz.kai
# FizzBuzz.
#
# Sum types + pattern match. kaikai is expression-oriented: `if`,
# `match`, and blocks all return a value.
#
#   $ kai run examples/quickstart/02_fizzbuzz.kai
#   1
#   2
#   Fizz
#   4
#   Buzz
#   ...
#   FizzBuzz

type Tag
  = Both
  | Fizz
  | Buzz
  | Other(Int)

fn classify(n: Int) : Tag {
  if n % 15 == 0     { Both }
  else if n % 3 == 0 { Fizz }
  else if n % 5 == 0 { Buzz }
  else               { Other(n) }
}

fn label(c: Tag) : String {
  match c {
    Both     -> "FizzBuzz"
    Fizz     -> "Fizz"
    Buzz     -> "Buzz"
    Other(n) -> int_to_string(n)
  }
}

fn loop(i: Int, n: Int) : Unit / Console {
  # `if` without `else` returns `()` implicitly when the condition
  # is false — common shape for "do work or stop the recursion".
  if i <= n {
    println(label(classify(i)))
    loop(i + 1, n)
  }
}

fn main() {
  loop(1, 15)
}
04_effect.kai
# Custom effect with a handler.
#
# Algebraic effects are kaikai's differentiator: a function declares
# the effects it uses in its type, and any caller has to either also
# declare them, or install a handler that provides them.
#
# Below, `Log` is a custom effect with one op, `log(msg)`. `greet`
# uses it but doesn't say HOW logging happens. `main` decides at
# call time: prefix every log with `[INFO]` and route to stdout.
#
#   $ kai run examples/quickstart/04_effect.kai
#   [INFO] hello, kaikai
#   [INFO] hello, world

effect Log {
  log(msg: String) : Unit
}

fn greet(name: String) : Unit / Log {
  Log.log("hello, " ++ name)
}

fn main() {
  handle {
    greet("kaikai")
    greet("world")
  } with Log {
    log(msg, resume) -> {
      println("[INFO] " ++ msg)
      resume(())
    }
  }
}
06_pipes.kai
# Familia de pipes: cuatro operadores, cuatro intenciones.
#
#   |>   apply       aplica una función al valor de la izquierda
#   |    map         mapea sobre la colección
#   ||   flat-map    mapea y aplana el resultado
#   |?   filter      conserva los elementos que cumplen el predicado
#
#   $ kai run examples/quickstart/06_pipes.kai
#   total=20

fn rango_loop(i: Int, n: Int, acc: [Int]) : [Int]
  = if i > n { list_reverse(acc) } else { rango_loop(i + 1, n, [i, ...acc]) }

fn rango(n: Int) : [Int] = rango_loop(1, n, [])

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() {
  let total = rango(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}")
}
07_uom.kai
# Unidades de medida: aritmética que no confunde monedas.
#
# Las unidades viven en el tipo. El compilador rechaza sumar USD
# con EUR; convertir entre monedas requiere un paso explícito.
#
#   $ kai run examples/quickstart/07_uom.kai
#   balance=845 USD

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}")
}
08_contracts.kai
# Contratos: precondiciones y postcondiciones en la firma.
#
# `requires` impide llamar a la función con argumentos inválidos.
# `ensures` exige que el resultado cumpla con una propiedad.
# Dentro de `ensures`, el nombre `result` es el valor devuelto.
#
#   $ kai run examples/quickstart/08_contracts.kai
#   5
#   3

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 == 0 - n
{
  if n >= 0 { n } else { 0 - n }
}

fn main() {
  println(int_to_string(dividir(10, 2)))   # 5
  println(int_to_string(abs(0 - 3)))       # 3
}