hanki

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.

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:

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:

  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, and a pure def may loop forever.
  2. 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.
  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 with 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 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.