nib

The signature is the whole story.

Nib is a small language with one idea: a function says what it needs and what it may do, the program is checked against that before it runs, and the one place a promise becomes an implementation is a line you can point at.

There is no framework, no dependency injection container, no annotation processor, and no mock library. There is an interface — what is needed, named. A module — the logic, allowed to name only the interface. An effector — the only thing that may hold state or touch the world. And a wire — the single seam where a promise meets an instance.

01 / the whole ideaOne file, four declarations, and nothing else

snippets/hello.nib
namespace Hello

-- 1. The promise. An interface says *what* is needed, never how it is done.
interface Greeting where
  hello(name: String) -> String

-- 2. The world. An effector is the only thing in nib that may hold state or
--    touch anything outside the program, and here it holds none.
sequential effector English is Greeting where
  hello(name) = "Hello, " ++ name ++ "!"

-- 3. The logic. Pure by construction: it declares the interface it needs and
--    cannot reach an instance even if it wanted to.
module Hello where
  func main :: -> String ! Greeting =
    @Greeting.hello("World")

-- 4. The wire. The one place a promise becomes an instance.
wire W where Hello.main with Greeting = Hello.English
What nibc prints when you run it
$ nibc --run snippets/hello.nib
Hello, World!

Notice what the program never does: it never constructs English, never imports it, never checks whether it exists. main calls @Greeting.hello and the wire decides who answers. That is the entire architecture, and every other page here is a consequence of it.

02 / four wordsThe vocabulary you need

interface
A name for a set of operations and their types. No body, no state, no default. It is a sentence about what a module is allowed to want.
effector
An implementation of an interface — the only place nib lets you write var or reach the file system, the clock, or a socket. Marked sequential when it has a lifecycle.
module
Your logic. It names interfaces and nothing else, so a mistake in the wiring cannot be a mistake in the logic, and vice versa.
wire
One line: with Io.Out = Io.Stdout. The only place an instance is chosen. Change the wire, change the world — the module is untouched, and the same file can be a test, a batch job, or a server.

03 / what it buysThree promises you can check

The receipt can't lie. Every function's signature lists the effects its body performs, and the compiler refuses a body that does more. A function that declares ! Store and quietly writes a log line does not compile — you get the diagnostic, not the bug in production.

Swapping the world needs no test doubles. Because implementations arrive through a wire and never through the call site, a fixture is an ordinary effector: one word in a wire line and the module runs against fixed data. Nothing is monkey-patched.

Cross-cutting behaviour is composition, not annotation. An effector can take another effector as a parameter and forward to it, so logging, counting, or retrying is a second instance the wire pipes in — Plain |> Counted — not a decorator the module has to know about.

Every guarantee on the next page ships with a program that proves it →

04 / statusWhat this actually is, today

v0.1.0OCamlno packages

Nib is a working research language, not a 1.0. The type checker, effect rows, loan checker, and HTTP server are real and this page is built with them: the site you are reading is a nib program, served by nibc --serve, from the same file that prints it. It is also single-threaded, has no TLS, and has no routing table — a 404 here is a line of nib, not a framework default.

Read the limits before you trust it with anything real; that page is written by the same people and is not marketing.

Write your first program → The guarantees Honest limits