Eve Functions

A function is a relation between input and output values. A function makes a computation to establish the output. Eve functions are objects of type Function. Eve enable functional programming paradigm (FP). Functions are the main subprograms of Eve. A subprogram that does not belong to a class is a function when it returns a result, and a procedure when it only does work and returns nothing, like print (see Procedures, on the previous page). The two words come from Ada. Methods belong to classes (see Methods). The kinds of subprograms and the rules of their parameters (by value, by reference, optional, vararg, order, results) are on the previous page, Subprograms.

Keywords of this phase

The keywords that phase 3, Functional Programming, adds to the core language (the core keywords are in Syntax). Each one is explained on the page in the last column.

KeywordMeaningExplained in
functiondeclares a function: a subprogram outside a class that returns a resultFunctions
generatordeclares a function that gives its values one at a time with yieldGenerators
yieldin a generator: gives a value to the caller and waits for the next requestGenerators
deferregisters a statement that runs when the enclosing subprogram ends: defer f.close();Processing

Function declaration

Functions are declared with keyword: "function", procedures with keyword: "procedure". A function always has a result; a procedure never has one. A module exports the functions and procedures other scripts may use (export (…), see Modules); a driver or an aspect uses its own.

Pattern:

#function declaration
function name(parameters) => (@result:TypeName) is
  ** executable block
  ...
return;

#procedure: no result, it does work
procedure name(parameters) is
  ...
return;

Two kinds of functions, and procedures

KindDeclaredCalledParallel
Plain function: a result, no side effects, deterministicfunction area(r: Real) => (@a: Real) isin an expression: area(2)the compiler decides (see Thread safety)
Stochastic function: a result that can differ for the same arguments (random, time, a changing module variable, a counting closure); it changes nothingfunction next!() => (@n: Integer) isin an expression: next!()the compiler decides (see Thread safety)
Procedure: no result, it does workprocedure log(message: String) isas a statement: log("start");not safe when it changes shared state

Notes:

  • A function always has a result and is used in expressions. A subprogram without a result is a procedure: it is a statement and needs no !, even when it has side effects (see Procedures). function without a result list, or procedure with one, is a compile error,
  • A procedure can have input/output parameters (@) and change them; a function changes nothing, also not its parameters. The result list and the parameters are explained in Subprograms,
  • A function can receive parameters of type function,
  • A function can create a function enclosed in it, called closure,
  • A function has a name: the keyword function must be followed by an identifier. A lambda expression is a notation that creates a function in a single statement,
  • A function can raise errors but it can't handle errors. Only a process can handle errors, so use preconditions to avoid runtime errors,
  • A function has no state between two calls; a closure keeps the state of the function that created it,
  • A function returns a result and changes nothing; a procedure changes something and returns nothing (command–query separation). A plain function is also deterministic: the same arguments always give the same result. A function whose result can differ for the same arguments is stochastic; its name ends with !: name!(). ! means only that: it says nothing about thread safety. The compiler decides for every function whether it is thread safe, deterministic or not (see Thread safety).

Execute function Function is executed in expressions using the name of the function followed by the list of arguments in round brackets (). Brackets are mandatory even if there are no arguments.

Pattern:


#demo function execution
primary aspect function_ex is

  ** declare functions
  function name1(param :Type,...) => (@result:Type) is
    let result := expression;
  return;

  function name2(param = value,...) => (@result:Type) is
    let result := expression;
  return;

  process main is
    ** execute using argument list
    new result1 := name1(arguments);

    ** execute using parameter names with pair operator (:)
    new result2 := name2(param:arg,....);
  return;
end function_ex;

The parameters (param: Type, param = value), the arguments and the way they are passed are explained in Subprograms. An argument can be a constant, a variable, an expression or another function call.

Calling a function

A function is called by its name followed by the arguments in round brackets. The brackets are mandatory, also when there are no arguments.

Two ways to call

  • In an expression, when the function has a result: new a := area(2);, print area(2) + 1;, or as an argument of another call: print area(sum(1, 2));;
  • As a statement, when it is a procedure: log("start");. A function called as a statement is not an error, but its value is lost, and the compiler warns.

Positional and named arguments

The positional arguments go first, in the order of the parameters; an optional parameter is given by name, name: value: greet("Eve", greeting: "Hi"). The rules and an example are in Order of parameters and arguments.

Recursion

A function can call itself. Every call has its own parameters and local variables. A recursive function has a case that ends the recursion; without it the calls never stop and the process ends with the unexpected-stop code (recursion too deep).


function factorial(n: Integer) => (@r: Integer) is
  if n <= 1 do
    let r := 1;                          ** the case that ends the recursion
  else
    let r := n * factorial(n - 1);       ** the function calls itself
  done;
return;

Functions can be recursive; processes can not (see Aspects).

Anonymous functions

A function with no name is a lambda expression (Expressions). A lambda is passed as an argument (Callbacks), returned by a function (Closures) or assigned to a name. It is never called where it is written: a function is first defined, and later it can be run.


** define the lambda, then call it by its name
new area := (r: Real) => (3.14159 * r * r);
print area(2.0);                                       ** 12.56636

** not Eve: a lambda called where it is written
** print ((r: Real) => (3.14159 * r * r))(2.0);      ** compile error
  • A lambda follows its own rules: expressions only, no state;
  • For more than one expression of work, write a named function: it can be tested and reused.

An anonymous function can't call itself: it has no name for the call, and Eve gives it none. A recursive function is written as a named function (factorial above).

Eve has no self-calling functions because (…)(…) is kept for a second set of parameters: the first set receives one or more data types and makes the type check flexible, for generic algorithms. It is the notation of the generic classes, Box(:Integer)(5) (see Generic functions and procedures). Generic functions and procedures come with Version 0.4.


** a generic function: the first list receives the type (Version 0.4)
function largest(:T <: Comparable)(items: ()T) => (@result: T) is
  ...
return;

new top := largest((3, 9, 2));               ** T is inferred: Integer
new word := largest(:String)(("b", "a"));    ** T is written

The type list at the call is optional when Eve can infer the types from the arguments. Procedures, methods and processes take a type list too. A lambda has no type list: when the work differs by type, write overloaded functions, one per type, since the formula is most probably different anyway.

Overloading (details in Generics). Functions, procedures and methods may share a name when their signatures differ. The signature is the number of parameters, their types and, for a function, the type of its result. Eve selects the signature at compile time, and the call must match exactly one, otherwise it is a compile error. A function and a procedure never share a name. Methods are the exception: a method without a result, called as a statement, and a method with a result, called in an expression, may share a name; they are different methods (Version 0.4).


function half(x: Integer) => (@result: Integer) is
  new h := x / 2 :Integer;   ** / gives a Real; the type converts it
  let result := h;
return;

function half(x: Real) => (@result: Real) is
  let result := x / 2.0;
return;

print half(8);       ** the Integer signature: 4
print half(9.0);     ** the Real signature: 4.5

Functional programming in Eve

Functions are the first of the pages of this phase; the table that compares all the subprograms is in Subprograms:


Read next: Generators