Eve Subprograms
Kinds of subprograms
Four keywords declare a subprogram. A lambda expression is not a fifth kind: it is a notation that creates a function in one statement.
| Kind | Declared with | Result | Side effects | Called with | Explained in |
|---|---|---|---|---|---|
| plain function | function | yes | no | f(x), in an expression | Functions |
| stochastic function | function name! | yes | no | f!(x), in an expression | Functions |
| procedure | procedure | none | yes | f(x);, as a statement | Functions |
| generator: a kind of function, or of method in a class | generator | one, given with yield | no | for v in g(x) | Generators |
| object method | method, in a class | optional | on its object | o.m(x) | Classes |
| extension method | method name(@self: Class, …), in a module or an aspect | optional | on its object | o.m(x) | Methods |
| unsafe method (changes shared state) | method name! | optional | yes | o.m!(x) | Methods |
| lambda | (params) => (expression) | one per expression | as the function it creates | through the function it creates | Lambdas |
- None of them handles errors: only a process does (see Processing);
- A process (
process main is) is not a subprogram, but it has a parameter list and follows the same rules as this page; - Most subprograms receive parameters, but not all of them need to. A procedure often has none:
procedure hello is … return;is calledhello;. When there are no parameters the parentheses of the declaration can be left out; the call of a function keeps them,random!(), the call of a procedure can drop them.
Parameters and arguments
The parameters are declared in round brackets after the name of the subprogram, separated by comma. A parameter is a local variable of the subprogram: it has a name, a type and, optionally, a default value. The arguments are the values that the caller gives to these parameters in a call. Arguments can be literals, constants, variables or expressions; for clarity we encourage local variables for arguments.
function sum(a, b: Integer) => (@result: Integer) is ** a and b are parameters
let result := a + b;
return;
new total := sum(1, 2); ** 1 and 2 are arguments
A subprogram has one list of value parameters. A second list in front of it is only a list of data types, for generic algorithms: largest(:Integer)(numbers) (Version 0.4, see Generics); f()() with two lists of values does not exist, and a lambda is never called where it is written. Functions, procedures and methods can also be overloaded: several declarations share a name when their signatures differ (see Overloading). Eve offers the same parameter features to every subprogram: functions, procedures, methods, generators, constructors and processes. The only difference is how they are called.
Forms of a parameter
| Form | Name | Meaning |
|---|---|---|
name: Type | mandatory | the caller must give an argument; several names can share one type: a, b: Integer |
name = value :Type | optional | a default value and an explicit type |
name := expression | optional | a default computed by an expression; the type is inferred from it |
*name: Type | vararg | collects the rest of the arguments in one collection |
@name: Type | input/output | receives a reference to a variable of the caller |
Passing by value
A parameter without @ receives its argument by value, also when the argument is a collection or an object: the subprogram works on its own copy, so the variable of the caller never changes. It behaves like the clone ::. The virtual machine may avoid the copy when nothing is changed (copy on write), but the meaning is always a copy. This is the default and the safest way to give data to a subprogram: nothing the subprogram does can surprise its caller.
Passing by reference
A parameter prefixed with @ is an input/output parameter: it receives a reference to a variable of the caller. The subprogram reads the value that the caller had, and a procedure can change it. Eve has no pure output parameters: every @ parameter is an input/output parameter. It behaves like the shared reference :=. The caller writes @ before the argument too, so a reference is never taken by accident: add(1, 2, @result), or by name add(1, 2, op: @result). The argument must be a variable, an element or a slice: @s[i], @nxt[a..b].
#demo input/output parameters
driver output_params is
** a procedure gives its output through @op
procedure add(p1 = 0, p2 = 1: Integer, @op: Integer) is
let op := p1 + p2;
return;
process main is
new result: Integer;
** input/output argument requires a reference to a variable
add(1, 2, op: @result);
print result; ** expected value 3
** negative tests, will fail
add(1, 2, result); ** error, "op" requires @
add(1, 2, @4); ** error, "op" requires a variable
return;
end output_params;
- By reference is the higher-level way to give an output; it is better than changing a global variable as a side effect;
- A function can't change its
@parameters, because a function changes nothing (command–query separation, see Functions): input/output parameters belong to procedures and methods; - A
@parameter has no default value, so it is never optional; - A generator receives inputs only, by value (see Generators).
Optional parameters
A parameter without a default value is mandatory. Mandatory parameters usually come first and optional parameters follow; a mandatory parameter may also come after an optional one, and then the call must give it by name (see Order of parameters and arguments). A parameter with a default value is optional: when the caller gives no argument, the default is used. The default is written with = and a type, or with := and an expression that gives the type. An optional argument is named in the call with the pair operator name: value.
function box(width: Integer, height = 1, depth = 1: Integer) => (@v: Integer) is
let v := width * height * depth;
return;
print box(2); ** 2: height and depth have defaults
print box(2, 3); ** 6
print box(2, depth: 4); ** 8: an optional parameter is given by name
Vararg parameters
A subprogram can receive any number of arguments of the same type into a single collection parameter. The name of the vararg parameter has the prefix *; *args is the usual name. The collection is a List, a DataSet or a DataMap, depending on the declared type.
- The first arguments are captured by the named parameters before it, the rest are captured by the vararg parameter;
- A subprogram can have one single vararg parameter;
- Optional parameters may follow the vararg parameter. At the call they must be named, otherwise they would be taken as one more element of the vararg: for
f(*x, y = ",")the call isf(x1, x2, x3, y: ";"). That is howprint (1, 2, 3, sep: " ")works.
The process main of a driver or aspect can receive parameters with the same conventions; the arguments of the command line become the arguments of the process:
# print all arguments
driver test is
process main(*args: ()String) is
for arg in args do
print arg; ** expect 1,2,3
done;
return;
end test;
eve -x test.eve 1 2 3
Order of parameters and arguments
The order of the parameters in the declaration is the order of the positional arguments in the call. We write the parameters in this order:
- mandatory parameters, the ones every call needs;
- optional parameters, with default values;
- the vararg parameter, if any (the optional parameters that follow it are always named);
- input/output parameters
@, usually the last ones.
The order is not forced: a mandatory parameter can be declared after an optional one, as the input/output parameter op above. The price is in the call: a mandatory parameter that follows an optional parameter is given by name, op: @result, because a positional argument could be taken for the optional parameter before it.
function area(a: Integer, b = 1: Integer) => (@r: Integer) is … ** mandatory first, optional after
procedure add(p1 = 0, p2 = 1: Integer, @op: Integer) is … ** mandatory after optional
add(1, 2, op: @result); ** op is named
add(1, 2, @result); ** error: op follows optional parameters, it must be named
In a call the positional arguments go first, in the order of the parameters. Mandatory parameters before the first optional one do not need a name. Optional parameters are given by name, in any order after the positional ones; naming any parameter is allowed and makes a long call easier to read. An argument of a @ parameter carries the prefix @, by position (swap(@a, @b)) or by name (op: @result).
Result parameters
The result of a function, of a generator or of a method is declared after the parameters, with the arrow => and a list in round brackets. A result is a parameter too, prefixed with @: the subprogram assigns it with let, and return; gives it to the caller.
function name(parameters) => (@result: Type) is
let result := expression;
return;
- The prefix
@forces a reference:new v := f(x);is valid becausevis a variable,2 := f(x);is not; - A subprogram can have several results:
=> (@r1: Type, @r2: Type). The value of the call is then a list(result1, result2), which can be captured in one statement:new (lo, hi) := range_of(v);; - A procedure has no result list. A
functionwithout a result list, or aprocedurewith one, is a compile error. The outputs of a procedure are its@input/output parameters; - A method may have a result or not. The result
(@self)returns the object itself and makes a method chainable (see Methods); a constructor has=> (@self)in its header (see Classes); - A generator declares the type of the values it gives, and gives them one at a time with
yieldinstead oflet; - A closure is given out as a result or as an output parameter that has the name of the enclosed function:
@next!andfunction next!()(see Closures);
Procedures
The procedure comes from the procedural programming paradigm, the oldest of the paradigms that Eve inherits: a program is a sequence of statements, grouped in named procedures that are called one from another and that share data through parameters and variables. The word is the one that Ada uses, and Eve keeps it exact: a subprogram that gives back a value is a function; a subprogram that only acts is a procedure.
Inherited, not original
Eve uses procedures, but not in their original design. In Eve a procedure can be combined with functions: it may call any function, and a function passed to it is a normal value. A procedure can hold state, in the variables of its module or of its script, and in the closures that it creates. For these reasons Eve can not demonstrate pure procedural programming, and this page does not try: it explains the procedure as it is in Eve, one tool among the others of a multiparadigm language.
Declaration
A procedure is declared with the keyword procedure, a name, an optional parameter list and the keyword is. The body is indented and the procedure ends with return;. It has no result list: a function without a result, or a procedure with one, is a compile error. A module exports the procedures that other scripts may use (export (…), see Modules); a driver or an aspect uses its own.
Pattern:
procedure name(parameters) is
** statements
return;
** no parameters: the parentheses can be left out
procedure name is
...
return;
Calling a procedure
A procedure is called as a statement, by its name, followed by the arguments in round brackets. When it has no arguments the brackets can be left out: foo;. A procedure is never used in an expression, because it has no value.
# procedures
driver process_demo is
new total = 0 :Integer;
** foo has no parameters
procedure foo is
print "I have no arguments";
return;
** add has a side effect: it changes the global total
procedure add(value: Integer) is
let total += value;
print "total is now {total}";
return;
process main is
foo; ** called without parentheses: no arguments
add(10);
add(20);
expect total == 30;
return;
end process_demo;
What procedures are used for
A procedure is the tool for everything that is not a pure computation. These are its usual jobs:
- Input and output: print a message, read a line, write a file, send data over the network or into a database.
print,writeandreadof the system library are procedures; - Changing state: update a module variable, a counter or a table that the program shares;
- Giving outputs through parameters: fill the
@parameters that the caller lends, instead of changing a global variable; - Splitting a program into steps: a long
mainbecomes a list of calls with good names,load; clean; report;. This is top-down design: write the big steps first, then each step in its own procedure; - Repeating an action: the same sequence of statements, written once and called many times;
- Set-up and clean-up: open resources, register
deferstatements, close them at the end (see Defer).
These are side effects, the reason why a procedure exists:
- modify a global variable, a module variable or the system state;
- open and write into an external file;
- print a message or accept input from console;
- upload something on internet or network.
Outputs of a procedure
A procedure returns nothing, but it can give results back through its input/output parameters, marked @ in the declaration and at the call (see Passing by reference). It is safer to use these parameters than global variables: the caller sees exactly which variables can change.
# a procedure with an output parameter, called by a process
driver report_demo is
** the input is passed by value, the sum is an output
procedure tally(values: ()Integer, @sum: Integer) is
let sum := 0;
for v in values do
let sum += v;
done;
return;
procedure show(label: String, value: Integer) is
print "{label}: {value}";
return;
process main is
new data = (3, 5, 8) :()Integer;
new sum: Integer;
tally(data, @sum);
show("sum", sum); ** sum: 16
return;
end report_demo;
Rules of procedures in Eve
- A procedure never ends with
!: it is made for side effects, so it is never deterministic;procedure p!()is a compile error; - A procedure may call any function or procedure, and may keep state (module variables, closures). A function can't call a procedure, because a function changes nothing;
- A procedure can't handle errors. Only a process can (see Processing); a procedure may raise them;
- Eve holds no references to procedures: a procedure is not a value, it can't be passed as a callback, stored or returned, and there are no procedure types. A callback is always a function (see Callbacks);
- A class body can't host procedures: inside a class the subprograms that do work are methods (see Classes);
- A procedure can create closures and give them out through
@output parameters; the closures are functions (see Closures);
Summary
| Subprogram | Inputs by value | Optional, vararg | @ parameters | Result list |
|---|---|---|---|---|
| function | yes | yes | no: it changes nothing | required |
| procedure | yes | yes | yes: its outputs | never |
| generator | yes | yes | no | required, given by yield |
| method | yes | yes | yes, besides @self | optional |
| process | yes | yes | yes: its outputs | never |
With these rules you can read every declaration in the rest of the tutorial. The next phase starts the functional paradigm: Functions.
Read next: Functions