1. What Hanki is
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 one keyword, def; the ! suffix on the name distinguishes them. 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 supported production modes.
use io
def greet(name: string) -> string
"Hello, #{name}"
end
def main!(args: List<string>) -> () [io]
io.print!(greet("world"))
end
What is distinctive
The points a reader arriving from another language is least likely to have met. Each one is developed in the section named.
- Effects are part of the type, and the boundary enforces them. A signature that says
[io]letshanki run --deny iorefuse to start the program, before it reaches the first write. (§6) - Failure is either a value or an effect, and one row covers both.
Resultwhere the caller should decide,throws Ewhere it should propagate. There is nounwrapand noexpectto reach for. No call site can promise that a failure will not happen. (§7) - Two tiers come from one front end, and parity between them is a requirement. A differential corpus compiles every example both ways and compares the output; a divergence is a failing test. (§22)
- The REPL is the language. A line at the prompt is type-checked and effect-checked as a line in a file is, and the session is live: bindings persist, and an actor spawned at the prompt is still handling messages at the next prompt. The same interpreter serves comptime evaluation and hot reload. (§15, §16, §22)
- Testing comes from the compiler. Tests, doctests, property tests, fuzzing and coverage are part of the language. A doc example that stops compiling is a build failure. (§20)
- The toolchain has no warnings. Every diagnostic it prints is an error that fails the command. There is no severity to configure, promote or ignore. (§1)
Diagnostics are errors
Hanki has one diagnostic severity: error. If the toolchain says something about your code, the command failed. There is no warning level, no -W family, no severity in the diagnostic model, and nothing to promote or demote. Whoever adds a check makes the one decision available: the condition fails the build, or the toolchain says nothing about it.
The rule governs the toolchain. A program reports failure to itself through Result, Option and throws (§7), which are unaffected.
Two kinds of output sit outside the rule. Neither one claims that anything is wrong:
- Requested information.
hanki check --explain-copiesreporting a field write that copies, or a???typed hole (§22) reported bycheck. Neither makescheckfail. A typed hole still marks unfinished executable code, and the executable-command policy is in §6. - Progress and results. What
hanki testprints as it runs, and whathanki buildreports on success. Anote:line belongs here: it reports what the toolchain did, and never affects an exit code.
No other output sits outside it. A module the entry cannot reach is error[H0627] and fails check and test. A project lists its intentionally unreachable files in the manifest's exclude list (§21). What remains reported is the defect the check is about: a module written test-first whose suite never runs. run and build do not report it, an orphan being no part of the program being run. hanki cddl names the types its schema left out as a note:; that schema is correct and the command succeeded.
The three layers
Hanki has one effect system, the effect row […] in a function's type (§6), and three layers of code built on it:
- 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, and a puredefmay loop forever. - Actions.
def foo!. Synchronous effectful computation: the!authorises effects and the row names which ones ([io],[throws E], and the rest). 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. - Actors.
actor Foo(§15). Concurrent, share-nothing entities with privatestateand a mailbox. An actor is built from actions: everyonhandler is an action with an effect row, implicitly[state], and only an action canspawnan actor or send it a message.
The layers nest. Pure code knows nothing of effects; actions add effects and remain synchronous and stack-based; actors add concurrency and isolation above actions. main! is itself an action, the program's root actor. Every effect in a running program therefore happens within some actor.
Actions and actors are separate constructs. Performing an effect is a synchronous call: it runs on the caller's own stack and returns a value. An actor is a spawned entity with its own identity, mailbox, thread, and deep-copied messages. The effect row is the one concept common to all three layers.