This chapter describes what a routine is when a program calls it: the kinds of routine and their headings, what a call evaluates and hands over, the ways a parameter receives its argument, what a function answers and how its body builds the answer, routines that call themselves and each other, and routines nested in routines. How a routine is declared, its defaults and the scopes its names are looked up in are chapter 3, which this chapter builds on rather than repeats. Each construct names its rule in the grammar, says what it means, and quotes what the compiler answers when a source breaks the rule. Procedures and parameters teaches the same ground from the start.

Every example is a complete program the website’s gate compiles with the compiler as it is written here, and the output shown beneath it is the output the program prints.

The kinds of routine

A routine’s heading names its kind, its name, its parameters in parentheses and, for a function, the type it answers (routine-heading):

KindHeadingWhat a call does
a procedureprocedure Report(count : int64);runs the body and answers nothing (procedure-heading)
a functionfunction Area(side : float64) : float64;runs the body and answers a value of the result type (function-heading)
a tasktask Worker(id : int64);runs beside the routine that schedules it, as chapter 11 describes
a generatorgenerator Readings() : stream of int64;answers a stream of the values it emits, as chapter 11 describes
a callbackcallback Visit(context : pointer Tally) : int32;is called back by a C library, as callbacks teaches

Any of them may end its heading with fails and an error domain, which states the failures the routine hands to its caller, as error domains teaches. Before the heading, exp exports the routine (chapter 2), heapless promises that it never uses the heap (heapless routines), and elemental applies a body written for one value to every element of a vector, a matrix or a tensor (chapter 3).

One name names one routine in a block: there is no second routine of the same name with other parameters, and a routine is told apart from another by its name alone.

WrittenCodeThe compiler’s words
a function without its result type, function F();2020expected colon and return identifier for function, found semicolon
a procedure with a result type, procedure P() : int64;2021unexpected return identifier for procedure, found identifier
a second Show beside procedure Show(n : int64);, procedure Show(s : string);5019procedure identifier already declared: Show
a routine named value, procedure value();2095'value' cannot name a routine: 'value (' always reads what the pointer in the parentheses points at, so no call of the routine could be written — name it 'Value' instead
a variable section behind a routine, in a block or in a routine2044a variable declaration cannot follow a function or procedure; move all of the block's constant, type, and variable declarations above its functions and procedures

Calling a routine

A call names the routine and its arguments in parentheses, one for each parameter in the order of the heading, apart from the trailing parameters whose default chapter 3 lets a call leave out (actual-parameters). A procedure is called as a statement, and a function as an operand of an expression, wherever a value of its result type may stand, or as a statement, where its answer is dropped.

A call may also give a value by its parameter’s name, the name and a colon written before the value, as a record value names a field (actual-parameter). The values by position come first, and once a call names a value it names every value behind it. Each parameter takes one value, by position or by name, and a parameter the call leaves out takes its default wherever it stands, so a call can give a later parameter and leave an earlier one out:

y := Clip(x, high: 2.0);
y := Clip(high: 2.0, low: -1.0, x: v);

The first call passes 0.0 for low, and the second gives all three parameters their values in an order of its own. A parameter without a default may also stand behind one with a default, as chapter 3 shows: the values by position fill the parameters from the left, so Pick(5, 2) writes both of its parameters and Pick(b: 2) leaves a to its default. The names a call writes are those of the routine’s heading, or for a routine imported from a library, a C library among them, those its contract gives the parameters. A sum type’s case takes its values by its fields’ names under the same rules, CommandTurn(quarters: 1, right: True), as sum types shows. An operation the language builds in, such as Length, has operands without names, so its values are written by position.

A call evaluates its arguments from the left, as it writes them, named ones too, each one before the routine is entered, then hands each to its parameter as the parameter’s mode says, and runs the routine’s body in a frame of its own: the routine’s parameters and variables are new at every call, its variables at their start values, and they end when it does. When the body ends, at its last statement or at a leave, the call ends, and a function’s call answers its result.

Each argument has the type of its parameter, or a type that type admits as chapter 4 states, and a constant argument is a value its parameter’s type holds.

WrittenCodeThe compiler’s words
a string for an int64 parameter, Show("x");5180incompatible parameter and argument data types at position 1 in call to Show: expected int64, got string
an int64 variable for an int32 parameter, Show(big);5180incompatible parameter and argument data types at position 1 in call to Show: expected int32, got int64
a constant a uint8 parameter cannot hold, Show(300);5233constant value 300 is outside the domain 0..255 of 'uint8'
a procedure’s call as a value, n := Hello();5104'Hello' names a procedure here and cannot stand as a value: a routine is invoked, never read as a variable — if a variable of this name lives in an outer scope, this declaration shadows it, so rename whichever one you did not mean
a value by position behind a named one, Clip(high: 2.0, 3.0)5777argument 2 of the call to 'Clip' is written by position behind the named argument 'high': once a call names an argument, every argument behind it names its parameter, so write the parameter's name and ':' before it, or move it ahead of 'high'
a name no parameter has, Clip(3.0, hgh: 2.0)5778'hgh' names no parameter of 'Clip', whose parameters are 'x', 'low' and 'high': write the name of the parameter the value is for before ':'
a parameter given twice, Clip(3.0, 1.0, low: 2.0)5779parameter 'low' of 'Clip' is given a value twice, by argument 2 and by argument 3: a call gives each parameter one value, so remove one of the two
a parameter without a default left out, Clip(high: 2.0)5780the call to 'Clip' leaves out parameter 'x', which has no default: write its value by position in its place, or by its name, 'x:' and the value
a name for an operation the language builds in, Length(text: "abc")5781'text:' cannot name a value in the call to 'Length': 'Length' is an operation the language builds in, whose operands have no names, so its values are written by position: remove 'text:' and write the value in its place
the arrow of another language, Clip(x, high => 2.0)1049'=>' is not an operator in Mica: a call names an argument with a colon, as in 'Clip(x, high: 2.0)'

A call with too many or too few arguments, and a call without its parentheses, are refused as chapter 3 quotes.

How a parameter is handed over

A parameter receives its argument in one of these ways (parameter-group):

DeclaredThe routine receivesThe caller writes
place : Pointits own copy of the value, a record or an array copied whole, which it may change while the caller’s value stays as it wasany value of the type
const limit : int64the value to read, never to assignany value of the type
place : pointer Pointthe address of the caller’s variable, which it reads and writes throughaddress spot
numbers : array[low..high : int64] of int64a view of the caller’s array, read in place, with its bounds in low and highthe array
numbers : pointer array[low..high : int64] of int64the same view, written throughaddress readings
numbers : span of int64a view of the caller’s elements, read in placethe array

A copy is the routine’s own: a routine that changes a value parameter changes nothing the caller sees. A pointer parameter is the road by which a routine writes a variable of its caller, and the caller grants it at the call with address, so every call that may write a caller’s variable says so where it stands, as value and pointer teaches. A field of a record behind a pointer parameter is read and written by its name, place.x := Target, and a scalar behind one with value, value counter.

const is a promise that the routine assigns nothing through the parameter. It stands on a value of a scalar, record or array type and on a pointer, through which the routine then writes nothing. A string is passed at the cost of two words whatever its length, so const adds nothing to it and the compiler refuses the word there.

An array parameter written with two bound names is a conformant array: it takes an array of any bounds whose index and element types match, and the routine reads the caller’s bounds in the two names. A span takes the elements of an array, a dynamic array or another span. Both read the caller’s storage in place and never copy it, so the routine writes through neither, and a conformant array that the routine writes is declared behind pointer, which const does not join. Conformant arrays and spans teach both views.

A call hands a place that a parameter can write to that parameter alone: two arguments of one call may not reach one place when the parameter receiving either of them can write it. A pointer parameter can write what it reaches, a borrowed pointer among them, unless const stands before it; a value parameter receives a copy taken before the call and never counts; and two elements of one array are two places only when their indices are two different constants. Ownership and borrowing teaches the rule with the way out of each refusal.

{
    Parameters - the ways a routine receives its arguments: a copy, a read-only value, an address and an array view.

    'Moved' changes its own copy of the point, so the caller's point keeps its place, and 'MoveTo' writes the caller's
    point through the address it was handed. 'Largest' reads an array of any bounds in place, its bounds arriving in
    'low' and 'high', and 'Scale' writes through the same view behind 'pointer', which the caller grants with 'address'.
    The arguments of every call are evaluated from the left before the routine is entered.
}
program Parameters;

imp
    WriteLn : std;

const
    CopyLine = "Moved answers x = %lf, and the caller's point is still at x = %lf";
    AddressLine = "MoveTo wrote x = %lf through the address";
    LargestLine = "the largest reading is %lld";
    ScaledLine = "scaled by %lld, the readings are %lld %lld %lld";
    Target = 9.0;
    Factor : int64 = 10;

type
    Point = record
        x, y : float64;
    end;
    Readings = array[1..3] of int64;

var
    spot, moved : Point;
    readings : Readings := (4, 16, 9);

{ The point moved to 'Target', computed in the routine's own copy of its parameter. }
function Moved(place : Point) : Point;
begin
    place.x := Target;
    Moved := place;
end;

{ The caller's point moved to 'Target', written through the address the parameter holds. }
procedure MoveTo(place : pointer Point);
begin
    place.x := Target;
end;

{ The largest of an array's elements, read in place whatever its bounds. }
function Largest(numbers : array[low..high : int64] of int64) : int64;
var
    index : int64;
begin
    Largest := numbers[low];
    for index := low + 1 to high do
        if numbers[index] > Largest then
            Largest := numbers[index];
end;

{ Every element of an array multiplied by 'factor', written through the view behind 'pointer'. }
procedure Scale(numbers : pointer array[low..high : int64] of int64, const factor : int64);
var
    index : int64;
begin
    for index := low to high do
        numbers[index] := numbers[index] * factor;
end;

begin
    moved := Moved(spot);
    WriteLn(CopyLine, moved.x, spot.x);
    MoveTo(address spot);
    WriteLn(AddressLine, spot.x);
    WriteLn(LargestLine, Largest(readings));
    Scale(address readings, Factor);
    WriteLn(ScaledLine, Factor, readings[1], readings[2], readings[3]);
end.

It prints:

Moved answers x = 9.000000, and the caller's point is still at x = 0.000000
MoveTo wrote x = 9.000000 through the address
the largest reading is 16
scaled by 10, the readings are 40 160 90
WrittenCodeThe compiler’s words
a variable passed for a pointer parameter without address, Bump(n);5180incompatible parameter and argument data types at position 1 in call to Bump: expected pointer int64, got int64
the address of a literal, Bump(address 5);5120memory operation 'address of' requires an identifier or field selection operand, found literal
a write through a const pointer, value p := 3;5146the parameter 'p' is declared 'const', so the routine cannot assign it: drop 'const' to give the routine a copy of its own to change, or assign the value to a variable of the routine
const on a string, const name : string5543parameter 'name' is a string, a two-word descriptor already passed at the cheapest cost: write 'name : string' — the constant mode admits a pointer or a plain value only
an element of a conformant array assigned, a[lo] := 1;5146the parameter 'a' is a view the routine reads in place, so the routine cannot assign it: declare it 'pointer array[lo..hi : int64] of int64' to write through it, or copy it into a variable of the routine
const before the writable view, const a : pointer array[lo..hi : int64] of int642060the writable view 'a' cannot be constant: 'pointer array[..]' exists to write through, so drop 'const' or drop 'pointer'
one counter for two pointer parameters, Twice(address a, address a);5784the call to 'Twice' reaches 'a' through parameter 'first', which can write it, and through parameter 'second', which can write it, and a place one parameter can write reaches the routine through that parameter alone: hand 'a' to one of the two, and give the other a place of its own
two elements chosen by computed indices, Swap(address row[first], address row[last]);5785the call to 'Swap' reaches two elements of 'row' through parameter 'first', which can write one of them, and through parameter 'second', which can write one of them, and their indices are not two different constants, so the compiler cannot tell the two elements apart: hand 'Swap' one 'address row' and the two indices, and let it select both elements

Results

Inside a function, its name is a variable of the result type (function-heading). It starts at the type’s start value, the body reads and writes it as any variable, a field or an element of it included, and the call answers the value it holds when the body ends. A function whose body never assigns its name answers the start value, zero for a number. leave ends the body where it stands, and leave when where its condition holds, so a function that has found its answer assigns it and leaves.

A function answers a value of any type a variable may have: a scalar, a string, a record, an array, a vector or a matrix. The call is an operand like any other, so a selector may follow it, Squares()[2], and a record or an array answered is copied into the variable it is assigned to.

{
    Results - what a function answers: a record, an array and a string, built through the function's name.

    Inside a function its name is a variable of the result type, so 'Midpoint' assigns the fields of its result one by
    one and 'Squares' its elements. A call is an operand like any other, and a selector may follow it. 'Find' answers
    the value its name holds when 'leave' ends it, and minus one when no square matches.
}
program Results;

imp
    WriteLn : std;

const
    MidpointLine = "the midpoint is at %lf, %lf";
    SquaresLine = "the squares are %lld %lld %lld, and the last one alone is %lld";
    GreetingLine = "%ls";
    FindLine = "the root of 49 is %lld, and 50 has none: %lld";
    Salutation = "hello, ";
    Name = "Ada";
    NotFound = -1;
    Searched = 10;

type
    Point = record
        x, y : float64;
    end;
    Triple = array[0..2] of int64;

var
    middle : Point;
    squares : Triple;

{ The point halfway between two points, built field by field in the function's result. }
function Midpoint(const a : Point, const b : Point) : Point;
begin
    Midpoint.x := (a.x + b.x) / 2.0;
    Midpoint.y := (a.y + b.y) / 2.0;
end;

{ The squares of 0, 1 and 2, built element by element in the function's result. }
function Squares() : Triple;
var
    index : int64;
begin
    for index := 0 to 2 do
        Squares[index] := index * index;
end;

{ A greeting for a name, joined into a new string. }
function Greeting(name : string) : string;
begin
    Greeting := Salutation + name;
end;

{ The number whose square is 'target', or minus one when no number up to 'Searched' is. }
function Find(const target : int64) : int64;
var
    root : int64;
begin
    Find := NotFound;
    for root := 1 to Searched do
        if root * root = target then
        begin
            Find := root;
            leave;
        end;
end;

begin
    middle := Midpoint((x: 1.0, y: 2.0), (x: 3.0, y: 6.0));
    WriteLn(MidpointLine, middle.x, middle.y);
    squares := Squares();
    WriteLn(SquaresLine, squares[0], squares[1], squares[2], Squares()[2]);
    WriteLn(GreetingLine, Greeting(Name));
    WriteLn(FindLine, Find(49), Find(50));
end.

It prints:

the midpoint is at 2.000000, 4.000000
the squares are 0 1 4, and the last one alone is 4
hello, Ada
the root of 49 is 7, and 50 has none: -1

Recursion

A routine may call itself, and two routines may call each other, since every routine of a block sees every routine beside it, above or below, as chapter 3 states. Each call has a frame of its own, so every level of a recursion keeps its own parameters and variables.

{
    Recursion - a function that calls itself, and two functions that call each other.

    'Factorial' calls itself until the number reaches one. 'IsEven' and 'IsOdd' call each other, which a block allows
    because every routine of a block sees every routine beside it, above or below. Each call has a frame of its own,
    so every level keeps its own 'number'.
}
program Recursion;

imp
    WriteLn : std;

const
    FactorialLine = "the factorial of %lld is %lld";
    EvenLine = "%lld is even";
    OddLine = "%lld is odd";
    Number : int64 = 10;
    Tested : int64 = 7;

{ The product of the numbers from 1 to 'number'. }
function Factorial(const number : int64) : int64;
begin
    if number <= 1 then
        Factorial := 1
    else
        Factorial := number * Factorial(number - 1);
end;

{ Whether a number that is not negative is even, asked of the number below it. }
function IsEven(const number : int64) : bool;
begin
    if number = 0 then
        IsEven := True
    else
        IsEven := IsOdd(number - 1);
end;

{ Whether a number that is not negative is odd, asked of the number below it. }
function IsOdd(const number : int64) : bool;
begin
    if number = 0 then
        IsOdd := False
    else
        IsOdd := IsEven(number - 1);
end;

begin
    WriteLn(FactorialLine, Number, Factorial(Number));
    if IsEven(Tested) then
        WriteLn(EvenLine, Tested)
    else
        WriteLn(OddLine, Tested);
end.

It prints:

the factorial of 10 is 3628800
7 is odd

A recursion runs as deep as the stack holds its frames. One that goes deeper stops the program with a trap report whose first line names the reason, Mica runtime failure: reason=stack_overflow (21), and whose next lines name the file, the line and the column of the call and show its source line, as the traps describe. A program that needs a deeper stack asks for it with --stack-size, which the command line lists.

Nested routines

A routine’s block may declare routines of its own behind its sections, and such a routine reads and writes the parameters and variables of every routine around it, is called by its routine’s statements alone, and nests at most sixteen deep, as chapter 3 states with its refusals. A nested routine’s call reaches the frame of the routine around it at that call, so a nested routine called by a recursion reads the variables of the level that called it.