hanki

1. Elevator pitch

Hanki is a statically-typed language with Ruby/Crystal-flavored syntax, Erlang-style actors, and a typed effect-marker system. Pure functions and actions share a single keyword (def); the ! suffix on the name is what distinguishes the two. Hot reload at actor boundaries is a primary feature.

The toolchain has two tiers, built from one front end: a bytecode interpreter with a REPL (hanki run), and an ahead-of-time LLVM compiler (hanki build). Both are equally supported production modes; §22 gives the parity requirement and how it is enforced.

use io

def greet(name: string) -> string
  "Hello, #{name}"
end

def main!(args: List<string>) -> () [io]
  io.print!(greet("world"))
end

The three layers

Hanki has one effect system - the effect row […] carried by a function's type (§6) - and three layers of code built on it:

  1. Pure functions - def foo, no !. No effects; cannot call an action; cannot crash. (Totality here means no effects and no crash - termination is not checked, so a pure def may still loop forever.) The effect-free base.
  2. Actions - def foo!. Synchronous effectful computation: the ! authorises effects and the row says which ([io], [throws E], …). Actions are the vocabulary of effects - every side effect a program performs is performed by an action, on the caller's own stack, returning a value directly.
  3. Actors - actor Foo (§15). Concurrent, share-nothing entities with private state and a mailbox. An actor is built from actions: every on handler is an action (it carries an effect row, implicitly [state]), and only an action can spawn an actor or send it a message.

The layers nest: pure code knows nothing of effects; actions add effects but stay synchronous and stack-based; actors add concurrency and isolation on top of actions. main! is itself an action - the program's root actor - so every effect in a running program happens within some actor.

Actions and actors are deliberately not one construct. Performing an effect (a synchronous call - cheap, returns a value) and being a concurrent entity (a spawned actor with its own identity, mailbox, thread, and deep-copied messages) are different operations; collapsing them would hide that difference, not remove it. The effect row is the single concept that unifies all three layers.