nib is v0.1: a research language with a real type system and several real gaps. This page is written by the people who built it, because the attractive version of each item below would be a lie — and a language that advertises guarantees should be judged by what it says it cannot do.
Where a claim is about a number in the source, check.sh greps the
file for it on every build, so this page cannot quietly go out of date.
Strings can be concatenated with ++, interpolated inside a
literal, and compared. That is the whole vocabulary: no slicing, no split, no
replace, no formatting — and no String namespace to call. Even this
site is assembled by concatenation for exactly that reason.
namespace App
module App where
-- Concatenation, interpolation and equality exist. A namespace of string
-- operations does not — v0.1 has no slicing, no split, no replace.
func main :: -> String =
String.replace("a", "b", "banana")
wire W where App.main with Io.Out = Io.Stdout
$ nibc --run snippets/strings.nib Name error at snippets/strings.nib:7:5: namespace 'String' is not known: no loaded file declares it (used: App)
concurrent is a declaration, not a threadsequential and concurrent describe whether an
instance's state has a lifecycle. Neither one starts a lane: the runtime is
single-threaded, nothing schedules a concurrent effector, and no
parallelism is inferred from the flags. Hoisting work out of a branch or a
failure point would execute effects that were never reached, so independence has
to be an explicit, checkable claim by the author — and independent
{ } is not built yet. What is built is the refusal: a
var on a concurrent effector is rejected rather than
accepted and quietly wrong (guarantee 07).
The server is part of the tool, and it is deliberately small: one connection
at a time, always Connection: close, no keep-alive, no chunking, no
TLS, and no routing table. These are design decisions and they are also limits
you have to plan around.
Listen { port: 8390 } binds 127.0.0.1; reaching the network takes
an explicit host: "0.0.0.0" where a reviewer will see it.respond(404).lib/server.ml says the quiet part itself: nothing here is
hardened for a hostile network. This page is nonetheless public, and it is
served by exactly the loop described above — that is a deliberate trade for a
v0.1 site whose whole point is to be served by the compiler it describes, not an
oversight. This site terminates TLS in front, as every app here does; if you
expose a nib server directly, you are the first line of defence — and you should
not.
pin is a promise about a revision, and no type keeps it truesystem/tools/nib/docs/boundary.md §7.5
pinmoves a decision from invisible to visible. It does not make the decision safe: a stored closure still runs the code of the revision that created it, and if that revision's dependencies moved, it may fail in a way nobody can reproduce. The best a language can do is refuse the silence — run the pinned value at its birth revision and say so, or refuse and demand an explicit migration. Anyone expectingpinto be a safety feature should read this paragraph twice.
Read the loan checker as a tool for making intent visible at every site, not
as a sandbox. It refuses the cases it can see — storing lent code, returning it,
putting it in a container, handing it to a pin parameter from a
lent one — and says nothing about what the code does when it finally runs.
Durability is protocol-shaped. Writing a run down is not enough: something
has to decide that the run should be picked up again — when, by whom, with what
retry, against which revision of the program. nib has the first half and none of
the second. --park FILE writes the rest of a run to a versioned JSON
frame and --resume FILE continues it in a different
process:
namespace Park
-- The same shape as hello.nib, parked instead of finished: --park writes the
-- rest of the run to a file, and a *different* process can pick it up.
interface Greeting where
hello(name: String) -> String
sequential effector English is Greeting where
hello(name) = "Hello, " ++ name ++ "!"
module Park where
func main :: -> String ! Greeting =
@Greeting.hello("World")
wire W where Park.main with Greeting = Park.English
$ nibc --run snippets/park.nib --park /tmp/nib-park-demo.json $ nibc --run snippets/park.nib --resume /tmp/nib-park-demo.json Hello, World!
What does not exist is anything that owns frames: no journal, no
queue, no scheduler, no retry budget, no record of which frames are outstanding.
Resuming is a command a person types. So the property you get is that a parked
run is not trapped in one process — and the property you do not get is
durability, which is a runtime, not a file format. A pin does not
change this.
There is no package manager, no registry, and no dependency graph. The
standard library is three nib files — io, http,
iter — and they are nib source, compiled by the same front end as
your program, which means you can read them, argue with them, and replace them.
Anything else you write yourself, and the type checker will make sure the seams
are explicit.
Two consequences worth naming: there is no Option type in v0.1,
so Map.get raises MissingField instead of returning
one; and the compiler gives you no fancier diagnostic than the ones printed on
these pages, which is why they are worth reading carefully.
check.sh runs every snippet, checks its exit status as well as
its words, diffs every quoted block against the file it claims to quote, and
greps the facts above — but it cannot check prose. If a sentence here stops
being true without a number or a program behind it, nothing will catch it: the
first version of this page said --park and --resume
moved a frame "within one process", which is false, and no grep noticed. Two
smaller caveats, stated plainly: if no nibc can be found, the check
fails rather than skipping, so a broken toolchain cannot pass quietly;
and ./check.sh --record rewrites the expected transcripts from a
live run, which is useful for an honest change and dangerous as a reflex — the
diff it produces is the claim you are changing.