It's now time to write the "hello world!" program in Cell:

Main(String* args) {
  Print("Hello world!\n");

Both Main and Print here are procedures. Their names follow the same syntactic rules as type names: an uppercase letter, followed by one or more letters (either upper or lower case) and numbers, with at least one lowercase letter in the mix and no underscores. Unlike functions, procedures can have side effects and perform I/O; they may or may not return a result or take any arguments, but the parentheses after their name are always required even if the argument list is empty. Procedures can call functions, but not vice versa, since functions are not allowed to have side effects.

All types of statements that can appear in the body of a function can also appear inside the body of a procedure, but there's a number of statements that can only be used with procedures. One of them is the "valueless" return; statement, used to explicitly return from a procedure that has no return value. Another one is the procedure call. A few examples:

res = FileRead("test-data/input-A.txt");
ok = FileWrite("test-data/output-A.txt", data);
ch = GetChar();

Note that procedure calls are statements, not expressions, and therefore cannot be nested inside expressions. If a procedure returns a value, the compiler forces you to store it in a variable, you cannot just ignore it (you are free to ignore the content of that variable afterwords, of course). The other statements that can be used inside procedures but not functions are all related to automata, and we'll discuss them once we get there.

Procedures cannot make use of type variables in their signature, cannot take closure arguments and cannot be used themselves as closures. They also cannot have explicitly declared local variables, but they can have local automaton variables (more on this in another chapter).

Main is where the execution of the program starts: you need to define one in your program in order to create a standalone application in Cell (as opposed to a bunch of C++/Java/C# classes that are then imported into an existing code base). It works just like it does in any other language: it always takes a single parameter, the program's arguments, as a sequence of strings, and if it returns a value, which must be of type Int, that value becomes the exit status of the whole process. If no return value is provided, the exit status is 0.

The only way to do any I/O is to use one of the builtin procedures described here. The I/O library will be expanded in the future, but it will always be very basic. Cell is designed to integrate with your primary programming language, not to write entire applications. Given that, it makes no sense whatsoever to try to reimplement in Cell the I/O capabilities of your programming language of choice. It's not just pointless, it would also be a huge drag on the development of the compiler. Procedures and some minimal I/O capabilities are there only because they are needed for testing, and when learning the language. They can of course also be used to write data-processing batch applications, like the Cell compiler itself, that read some input data from one or more files, process it and then write the results to another set of files, but nothing more than that.

Future versions of the language will allow one to write some very specific types of applications entirely in Cell, but that will be done using a completely different programming model. Think something along the lines of The Elm Architecture, but for (possibly distributed) back-end applications, instead of front-end ones. You can find more information in the network architecture overview.