My first Archon program
The smallest complete program, line by line, to run and to change.
Here is a complete Archon program. Run it as it is, then change the text and run it again.
@entry(start)
object MyProgram {
start() {
Console.print("Greetings archonites!");
}
}
Line by line:
@entry(start)— the directive that names the starting point. An Archon program starts at the method@entrynames, on the type that contains it. Here, the methodstartis the one that starts. Historically, many languages made the name of the method the convention: in C, and in everything that followed it, the program starts atmainbecause it is calledmain. Archon says it with a directive rather than with a reserved name, and the consequence is direct: the entry point carries whatever name you want.starthere,mainif you insist,launchorbegin—@entryis what decides.object MyProgram { … }— the type that carries the program.objectdeclares an object: a type of which there is never more than one instance, and that instance already exists when the program starts — nothing instantiates it, there is no constructor to write. Which is exactly what a program is: there is only one. In Archon, no statement lives at file level: there are no free functions, so there is no free code either. Everything that runs belongs to a method, and every method belongs to a type.start() { … }— the starting method. Nostatichere: the single instance ofMyProgramis already there, sostartis an ordinary instance method. On anarchetype— the type with several instances, the central concept of the language, which section 7 introduces soon —@entrywould have to name astaticmethod, since no instance exists at that point.starttakes no parameter and returns nothing.Console.print("Greetings archonites!");— the statement that prints. The argument is passed positionally here, by its rank; Archon also lets you name it, which the next sub-section shows. The statement ends with a semicolon.
The compiler checks what the directive promises: the named method does exist in MyProgram
(otherwise P038), and it takes no parameter (otherwise P041).
The @ is a directive. @entry is the first of a family: in Archon, everything that
starts with @ is a precompilation directive — an instruction addressed to the compiler,
read before it produces the program, and never code that runs. Unlike .NET attributes or Java
annotations, it is not an open mechanism where everyone invents their own: the set is
closed, recognised natively by the compiler, each one with a precise and documented effect.
They serve two purposes: acting on compilation — @entry names the starting point, @mode
changes the memory model, @extern binds a function from a library written elsewhere — and
documenting, with @doc, which we will see below.
An object is that unique instance without the ceremony. The need is as old as
object-oriented languages: a type of which only one instance must ever exist — the singleton
pattern. In Java, in C#, in C++, the language does not offer it — it is built, at the price
of a ritual everyone has written ten times: make the constructor private so nothing else can
instantiate, keep the sole instance in a static field, expose it through a getInstance()
method that creates it on first demand, and finally wonder what happens if two threads call
that method at the same time. Half a page of plumbing to say “there is only one” — and a
guarantee that holds only as long as nobody works around the convention. Scala and Kotlin
showed the way out by making it a keyword; Archon takes the same one: object, and that is
all. It is no longer a discipline to keep, it is a property of the type — nothing can create
a second one.
Note that only the keywords are fixed by the language: start and MyProgram are names you
choose. The French course runs the same program with French names — demarrer, MonProgramme —
and it behaves identically.
Your turn
- Change the text between the quotes.
- Add a second
Console.print(…);line under the first. - Rename
starttomainin the method — but not in the directive. Run it, and read what the compiler tells you.
Naming the arguments
The same program, except that the argument is passed by its name.
Here is the previous program, with one detail changed: the argument of Console.print is
passed by its name.
@entry(start)
object MyProgram {
start() {
Console.print(text: "Greetings archonites!");
}
}
Run it. The output is exactly the one of the previous version — both spellings mean the same call.
A parameter has a name, and that name can be used at the call
print is declared static print(text: str): a single parameter, named text, of type
str. Until now that name was only of use inside the method; Archon lets you use it at the
call too. Hence two ways of passing a value:
- positional —
Console.print("Greetings archonites!"): the value is placed by its rank. The first value goes to the first parameter, the second to the second. - named —
Console.print(text: "Greetings archonites!"): the value is placed by the name of the parameter it targets, followed by a colon.
What comes before the colon is always the name of the parameter as the method declared it — never the name of the variable being passed.
The order no longer matters
With a single parameter, the difference does not show. It appears as soon as a method takes
several — and since Console.print takes only one, let us write one. greet takes two
parameters, firstName and lastName, and start calls it three times:
@entry(start)
object MyProgram {
start() {
self.greet(firstName: "Simon", lastName: "Magus");
self.greet(lastName: "Magus", firstName: "Simon");
self.greet("Simon", "Magus");
}
greet(firstName: str, lastName: str) {
Console.print(text: "Hello " + firstName + " " + lastName + "!");
}
}
Run it: three identical lines. The three calls designate the same method with the same
values — named in declaration order, named in reverse order, positional. Of greet, keep
only its first line for now: two parameters, each with its name and its type, exactly like
print and its text. The rest will come in due time — just note that self is the object
itself: self.greet(…) is MyProgram calling its own method.
Both forms even mix within a single call, provided the positional ones come first:
self.greet("Simon", lastName: "Magus"); // valid
self.greet(lastName: "Magus", "Simon"); // refused: error T027
The refusal is not an implementation convenience. A positional argument is placed at its
rank, and a named argument before it makes that rank impossible to read: in the second call,
"Simon" sits at the second rank, but the only slot still free is the first one. Should it be
read by its rank, or as “the next free slot”? Rather than picking one of the two readings
silently, the compiler refuses — and it refuses even when both readings would agree, so that
the rule can be read without thinking.
Why name
A named call says what each value is, without having to go back and read the method’s declaration. Above all, it survives the addition of a parameter: the name still targets the right one, where a positional call changes meaning without saying so. It is the recommended form as soon as a method takes more than one argument — and systematically for generated code, which has nobody to proofread its calls.
Your turn
- Remove
text:from the first program and run it again: the positional version does exactly the same thing. - In the second one, swap the two values of the third call:
self.greet("Magus", "Simon"). The program runs, and greets somebody else — to notice it by reading the call, you have to remember the order ofgreet’s parameters. Make the same mistake in the first call,firstName: "Magus", lastName: "Simon": it is plain to see, with nothing to look up. That is what a named call buys. - Write
Console.print(txt: "…")— a parameter name that does not exist — and read what the compiler answers: two diagnostics,T022thenT024. Why two?
Comments and documentation
What the compiler throws away, what it keeps — and who benefits.
The program from the beginning, unchanged in what it does, but annotated.
Run it: the output is exactly the one from the beginning of the section. Neither the comments nor
@docchange what the program does. They change what can be read from it.//— a line commentEverything that follows
//until the end of the line is ignored by the compiler. A comment only lives in the source file; nothing goes looking for it elsewhere./* … */— a block comment, which nestsThe second form runs over as many lines as needed, and it nests: an inner
/*opens a level, a*/closes one, and the comment ends when the count returns to zero.Run it: same output again. The region set aside itself holds a comment, and that does not end it early — which is precisely what nesting buys. In a language that follows the C rule, the first
*/closes: the lineend of what is set aside */becomes code again, and you collect a syntax error that talks about nothing. Archon counts levels rather than choosing silently.An opening never closed is an error,
L010, reported at the position of the/*— where you have to go and fix it, not at the end of the file where the compiler notices.@doc("…")— a description attached to the declaration@docis a directive, like@entry: it sits above a declaration and attaches a text to it. There are two in the program — one on theobject, one on the method — and@docapplies to any declaration: a type, a method, a field, a constant.The difference with a comment is not the tone, it is the grip. A comment floats next to the code, on the line where it was left;
@docis hooked to one precise declaration, and the compiler knows which one.It is not a magic command
@docprints nothing, checks nothing, does not change the program one bit. It files a description where tools know to go and find it — and that is all that is asked of it.That is where the difference lies. A tool that wants to say what
startdoes has nothing to guess: it does not have to pick which of the surrounding comments applies, nor hope that a document filed elsewhere is still current. The description is attached to the declaration, and the compiler knows which one. And an AI reading your code — to help you write it, review it, fix it — gains the same way: the intent is there, where it belongs, instead of being guesswork.A variant exists where the text lives in a separate file rather than in the code (
@doc(file: …, label: …)) — useful when the description is long. We will see it later; here, the text is in the code, in plain sight.The trap of the comment, and what
@doctakes out of itA comment is useful, and that is exactly what makes it dangerous. The compiler does not read it: it will never tell you that it has become false. Code that is changed without touching the comment beside it ends up lying with authority — the reader believes the sentence, it looks like it was written on purpose, and it takes them longer to understand the code than it would have taken with no comment at all. The block makes the risk worse, because it hides a lot at once, and the longer it is the less it gets re-read.
Hence the question to ask of every comment you are about to write: does it describe a declaration? If it does — what this method does, what this field holds, what this type is for — its place is
@doc, hooked to it. There it stays in the right place when the code moves around it, a tool knows how to find it, and renaming the declaration does not leave it orphaned. In a classic language, that is a large share of what gets written as comments.What is left to the comment is what describes no declaration: a note about a trick in the middle of a method body, a reason for doing something other than the expected, a region set aside for the time of an experiment — the block’s case, above.
The clearest witness is Archon’s own compiler: there is not a single comment in its
.arcfiles, and an automatic check refuses the repository if anyone adds one. Everything is in@doc.Your turn
//of your own, and run it: nothing moves.*/from the “end of what is set aside” line and run it. The compiler answersL010, and it points you at the/*that lacks a closing — not at the end of the file.@docand run it. The output does not change: that is exactly the point.