Skip to content

Latest commit

 

History

123 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

alt text

Avantgarde

A language where the whole program moves with a clock.

Component based, general purpose, declarative language, inspired by hardware description languages. An Avantgarde program is not a sequence of instructions — it is a circuit. Running it means ticking it.

Status

Under (re)specification, 2026 edition. Earlier attempts (2017–2018) produced the sketches in spec/flow/ and a module system draft; the project then hibernated. The restart is built around the one idea that was always the point and never got written down properly: global clock semantics. New documents live in design/, new examples in spec/clocked/.

No compiler yet, on purpose. Think first, then do :)

The pitch

  • Everything is a component. Inputs, outputs, a wiring diagram. No functions, no objects, no statements. You craft chips and plug them together.
  • Time is explicit. The program advances in ticks. Within a tick, values flow through pure wiring; at the tick's edge, all registers update simultaneously. There is no "during" — so there are no read-modify-write bugs, no interleavings, and swapping two registers needs no temp variable.
  • State is declared, not acquired. state: blocks are the only memory in the language. No heap, no variables. Wires and registers.
  • Effects are wires. Components never perform IO; they emit requests on effect pins. Only the top-level board file connects pins to real peripherals. Don't connect the pin — the effect cannot happen. Afraid to launch a rocket in Avantgarde? It is impossible (spec/clocked/rocketGuard.av).
  • Concurrency is clock domains. A component on its own clock is an actor: private registers, message queues at the boundary, no shared instants, no races. Actors weren't added to the language; they fell out of refusing to lie about simultaneity.
  • Tests are testbenches. Drive inputs over ticks, assert on output waveforms, state temporal properties (always, never, within). Failures replay exactly — determinism is total.
  • Specified down to Nand. The semantic model bottoms out at gates; Num and friends are intrinsics that behave as if built from them. Nothing is magic, some things are merely fast.

A taste

package: Examples/Clocked

component: Counter

input: enable : Bit, reset : Bit
output: value : Num

state:
  count : Num = 0

functionality:
  +(a <- count, b <- 1, out -> incremented)
  cond(if <- enable, then <- incremented, else <- count, result -> kept)
  cond(if <- reset, then <- 0, else <- kept, result -> count)
  id(a <- count, out -> value)

More: counter · blinker · traffic light FSM · echo · fibonacci in time · an actor · a testbench

Design documents

The spec is written as a design journal with chapters — see the index. Highlights: 01-clock (read this one first), 05-effects, 07-domains, 08-testing.

Module system follows the 1ML lineage (components and modules are one kind of thing): https://people.mpi-sws.org/~rossberg/papers/Rossberg%20-%201ML%20--%20Core%20and%20modules%20united%20[JFP].pdf

Why?

Aren't you tired of the same problems everywhere? How to test, how to structure a project, which abstraction to pick, how to make the code comprehensible? Avantgarde is an opinionated bet: one primitive (the clocked component), one sequencing mechanism (the tick), one composition mechanism (wiring). As Go simplified by removing, Avantgarde simplifies by removing more — including the call stack. You will never need an AbstractFactory, an IOC container, a Monad or an MVC, and this time the README can point at the design docs that explain why.

Open questions

Tracked honestly. Scoreboard as of July 2026 — the four questions the 2018 README left open are now all answered:

  1. Typeclasses vs 1ML modules? — resolved: no typeclasses. Signatures + wiring-time with bindings do the whole job (04-interfaces.md, epitaph in the journal graveyard).
  2. What type system? — static, inferred, Bit at the bottom, time in the types via wire/reg, Same(a) polymorphism (02-types.md).
  3. Isn't circuit execution hopelessly slow? — the answer is change propagation: only the cone of influence of changed inputs re-settles, a quiet tick costs zero (13-execution.md). Unproven until benchmarked, but no longer unanswered.
  4. Effects as algebraic effect stacks? — yes, and better than hoped: a handler stack is a series circuit, handlers are chips on the wire (05-effects.md, spec/clocked/logger.av).

The honest new open questions, one per scary:

  • Closures. Helper components can't see parent wires; leaning toward nested components with lexical wire scope (journal, May 18). The one hole in the core design.
  • Capacity inference. List<t, cap> everywhere means "cannot OOM, by theorem" — if and only if inference keeps the caps out of your face (11-memory.md).
  • Sugar. The no-expressions rule is the language's soul and occasionally its tax (journal, Jun 21). A desugars-to-wires expression layer is now scheduled before the parser, not after.
  • Smaller ones live as -- open: notes inside each design doc, on purpose — a question belongs next to the thing it doubts.

If you have design suggestions, open an issue. Some of the old specs are out of date, so be careful.

About

Component base, general purpose, declarative language.

Topics

Resources

Stars

10 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors