Lowent Manual←↑→

1 What Lowent sets out to do

The need for this chapter, and its context

When the first chapter of a new language opens with syntax, the reader memorises words and misses why those words look the way they do. Most of Lowent’s syntax follows from a single goal — reading only the head of an op should tell you what that op can and cannot do. With that goal in place first, the unfamiliar rules that come later (clauses closed by a full stop, fn marking purity, capabilities handed in as arguments) stop looking like arbitrary taste and start looking like faces of one design. So the goal and the way to read this book come first.

By the end of this chapter

You will learn what problem Lowent was built to solve and the five ideas that make up its answer (marking purity, contracts, effects paired with capabilities, memory without a garbage collector, and cross-checking two back ends). You will also settle that the language is still experimental, how the examples in this book are verified, and which devices to read it with.

The questions this chapter answers

  1. If the head has to say that much, doesn’t the code get longer?
  2. If the grammar is that narrow, how do new features get in?

1.1 Can you tell from the head alone?#

The thing people who fix programs do most is find out what someone else’s function does before calling it. It is called parse_header, but does it open a file? Does it allocate? How does it report failure? Does it start a thread? In most languages the answers live in the body. You read the body, then the bodies of the functions it calls, then the level below that.

Lowent lifts those answers up into the head. Here is the first op head this book meets.

proc main input out cap io . output u8 . effects io .

Even without knowing the words, some things can be read. This op is a proc (it is not pure). It receives an io capability under the name out. It returns a u8. It performs an effect called io. What is not written can be read too — this op does not allocate (there is no alloc), has no file-system capability, and starts no threads (there is no concurrent). What is not written cannot be done. The compiler checks it.

Let us look at a small program that actually runs.

examples/ch01/heads.low

module heads .
rem run: main

rem fn: the same input always gives the same answer, and it touches neither the screen nor files
fn area input w u64 . input h u64 . output u64 .
  requires le w 1000 .
  requires le h 1000 .
do
  return mul w h .
end

rem proc: may leave traces outside; what it does (effects io) and who allowed it (cap io) are visible in the head
proc main input out cap io . output u8 . effects io .
do
  let a u64 be area 3 4 .
  let n u64 be write_out out 1 "area is twelve\n" .
  return narrow u8 a .
end

Output

$ lowentc --run main heads.low
area is twelve
main() = 12

Now let the head lie. An op declared as a pure fn tries to write to the screen on the quiet.

examples/ch01/mistake_hiddenio.low

module mistake_hiddenio .
rem expect: E-EFFECT-CALC

rem ✘ declared as fn, yet it tries to write to the screen on the quiet
fn area_loud input out cap io . input w u64 . input h u64 . output u64 .
  requires le w 1000 .
  requires le h 1000 .
do
  let n u64 be write_out out 1 "computing\n" .
  return mul w h .
end

Output

$ lowentc --check mistake_hiddenio.low
mistake_hiddenio.low:8:1 E-EFFECT-CALC: this fn is declared pure but performs `io` — make it a `proc` with `effects …`, or remove the effect

The compiler refuses without running anything. The single word fn is a promise that “this op does not reach outside”, and when the body breaks that promise, translation stops. So whoever calls a fn need not open its body. To fix it, say what is true: proc area_loud … effects io ..

Q. If the head has to say that much, doesn’t the code get longer?

A. It does. The reading gets shorter in exchange. An op is written once, but it is read at every place that calls it, on every day someone fixes it. Lowent bets on that asymmetry. And because the compiler checks every clause of the head, the head does not go stale the way a comment does.

1.2 Five ideas#

Five mechanisms make the head trustworthy. Parts II to VIII of this book unfold them one by one.

IdeaWhat the head saysWhere
Separate pure from impurefn or procchapter 2
Write promises and have them checkedrequires · ensures · errorschapter 14
Pair what is done with who allowed iteffects · input … cap …chapters 15 and 16
Keep memory safe without a collectorregion · owned · refchapters 18–20
Run two ways and compare(a tool, not the head)chapter 31

Table 1.1 — The five ideas behind Lowent

Marking purity. A fn is pure — the same inputs give the same output and it leaves no trace outside. A proc may not be. Which one it is is always written. If a pure fn tries to print, the compiler rejects it.

Contracts. An op writes what it requires of its caller (requires) and what it guarantees on return (ensures). When the values are constants, the check happens at translation time; otherwise it happens at run time, on entry and on return. A proven contract removes checks in the body — a contract is both documentation and grounds for optimisation.

Effects and capabilities. An op declares the marks it leaves on the world with effects, and is handed the capability to do that work as a cap input. Lowent has no ambient authority — no global output or global allocator that can be used secretly from anywhere. If a capability is not visible in the argument list, the op cannot do that work.

Memory without a collector. To use memory safely without a garbage collector, Lowent ties the lifetime of values to regions and checks a borrowing rule of one writer or many readers. For machines without an operating system there is a separate path that only allocates from a fixed window that never grows.

Cross-checking two back ends. The compiler, lowentc, runs the same intermediate representation two ways. One is a virtual machine (VM) that runs by itself; the other is native code, emitted as C and turned into machine code by a C compiler. If the two give different answers, that is a compiler defect. Every example in this book passed this cross-check.

A common misconception. Lowent proves everything statically

It does not. Borrowing, regions and integer widening are stopped at translation time, but bounds checks in unproven places, overflow, and contract checks are stopped at run time. It is a mixed design. What is proven and what is not is gathered in Part X — Chapter 50 is devoted entirely to what is not proven.

1.3 One meaning, one spelling#

The entropy in Lowent’s name is the measure of disorder. The language tries to reduce the places where one meaning can be written several ways. A few choices follow.

With one spelling per meaning, people and tools read the same code in the same shape. The examples in this book all look alike not because of the author’s taste but because the language only allows that shape.

Q. If the grammar is that narrow, how do new features get in?

A. Instead of adding words, a feature comes in as one more clause in a frame that already exists. pipe is not a new loop syntax but a frame that writes one word per line inside a do … end block (chapter 24), and parallelism is not a new construct but a parallel clause in an op head (chapter 27). That is why the clause-order table is one of the most important tables in this language (chapter 3).

1.4 Where the language stands#

Lowent is an experimental language. It has not reached version 1, and its grammar and standard library still change. This book is written against the grammar the compiler in the repository actually accepts today, and when the compiler changes the book follows. That is why the book has its own edition number.

The compiler is written in C23, and its only external dependency is a vendored copy of proven_c_lib. Native code is emitted as C and built with the system’s C compiler, so it usually runs wherever there is a C compiler, and microcontrollers without an operating system are among its targets (chapter 30).

In practice. How the examples in this book are verified

Every .low file in this book lives in docs/manual/examples/, and each carries a one-line directive at the top. With rem run: … it must be run on both the VM and native code and give the same output; with rem expect: E-… the compiler must reject it with exactly that diagnostic; with no directive it must pass --check. The run results on the page were not copied by hand — they are exactly what the verification script left behind.

1.5 How to read this book#

This book was written with two kinds of reader in mind.

Readers new to programming read from Part I in order. Every piece of syntax comes with both “what it does” and “why it looks this way”, and each example is followed by an explanation of what its lines do. The “Common mistakes” at the end of Chapters 2 to 37 show the errors beginners really run into, with their diagnostics as they are. There is no need to fear error messages. In this language a diagnostic does not say “you are wrong”; it says “this is where the code differs from its promise”. Learn the first words from the table below.

Readers who have used other languages can follow just the example code and each chapter’s “syntax at a glance” table. The examples and that table show the chapter’s syntax together. If you know C, Parts V and VIII will come easily; if you know Rust, the borrowing rules of Part V will look familiar. Places where an idea carried over from a familiar language goes wrong here are collected in the “A common misconception” boxes.

WordMeaning
source · moduleone .low file a person writes · the named unit that file forms (chapter 21)
compiler · translationthe program that reads the source, checks it and turns it into something runnable (lowentc) · that work
diagnostican error (E-), warning (W-) or note (N-) from the compiler. Its name is the key to look it up (Appendix B)
value · typeone piece of data such as a number or text · what kind it is and how many bits (u64 is an unsigned 64-bit integer)
local namea name given to a value: let if it never changes, var if it does (chapter 6)
opa named piece of work; a function in other languages. fn if pure, proc otherwise (chapter 5)
effect · capabilitythe trace an op leaves outside (effects io) · the permission to do that (cap io) (chapters 15 and 16)
contractconditions an op requires before it takes a call and ensures when it returns (requires · ensures) (chapter 14)

Table 1.2 — First words

Every chapter opens the same way. It lists the earlier chapters it leans on (What to know first), asks one question that makes you recall them, says why the chapter sits where it does and what you will have by the end, and then shows the questions the chapter answers. The devices you meet inside the text are these.

DeviceUse
Q & AA question likely to come up while reading, and its answer
A common misconceptionA plausible but wrong idea, and why it is wrong
In practiceSomething that actually happened in the language or its standard library
The mathematicsA formal explanation you can skip without losing the thread
DemonstrationAn example file and the actual output left by the verification script

Table 1.3 — Devices used in the text

On a first reading, take Parts I to IV in order. After that you can jump to whichever part you need. For the standard library go to Part IX; to find out how far this language’s claims are true, go to Part X.