Section 1.2

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 @entry names, on the type that contains it. Here, the method start is 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 at main because it is called main. 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. start here, main if you insist, launch or begin — @entry is what decides.
  • object MyProgram { … } — the type that carries the program. object declares 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. No static here: the single instance of MyProgram is already there, so start is an ordinary instance method. On an archetype — the type with several instances, the central concept of the language, which section 7 introduces soon — @entry would have to name a static method, since no instance exists at that point. start takes 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 start to main in the method — but not in the directive. Run it, and read what the compiler tells you.
Section 1.2.1

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 of greet’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, T022 then T024. Why two?
Section 1.2.2

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.

@doc("The first program of the course: it greets the archonites.")
@entry(start)
object MyProgram {

    // The method @entry names: this is where the program begins.
    @doc("Writes the greeting on standard output.")
    start() {
        // One line of text, and the program stops.
        Console.print("Greetings archonites!");
    }
}

Run it: the output is exactly the one from the beginning of the section. Neither the comments nor @doc change what the program does. They change what can be read from it.

// — a line comment

Everything 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 nests

The 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.

@doc("The first program of the course: it greets the archonites.")
@entry(start)
object MyProgram {

    @doc("Writes the greeting on standard output.")
    start() {
        /* Set aside for the time of an experiment:

             Console.print("Hello, world!");
             /* we had noted here why this line existed */

           end of what is set aside */
        Console.print("Greetings archonites!");
    }
}

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 line end 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

@doc is a directive, like @entry: it sits above a declaration and attaches a text to it. There are two in the program — one on the object, one on the method — and @doc applies 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; @doc is hooked to one precise declaration, and the compiler knows which one.

It is not a magic command

@doc prints 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 start does 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 @doc takes out of it

A 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 .arc files, and an automatic check refuses the repository if anyone adds one. Everything is in @doc.

Your turn

  • Add a // of your own, and run it: nothing moves.
  • In the second example, remove the */ from the “end of what is set aside” line and run it. The compiler answers L010, and it points you at the /* that lacks a closing — not at the end of the file.
  • Change the text of a @doc and run it. The output does not change: that is exactly the point.