A function is a parameterized expression or a sequence of statements. Functions are first-class values. Functions can be stored in variables, records, and arrays; functions can be passed as parameters to other functions; functions can be returned as the results of functions. Functions can not be sent in messages to other actors. Function objects are immutable values. A function introduces a new scope: variables defined in a functions are not visible outside of the function, but will be visible to inner functions.
Function objects are made with the ƒ
florin operator which takes a parameter list and a body and produces a function object.
function_literal
'ƒ'
space function_name parameter_list space body
function_name
""
name
A function may be given a name for recursion and documentation.
The parameter list can be open (each parameter name on its own line) or closed (all parameters names on the same line, separated with ,
comma).
The parameters are names that will be bound to the arguments when the function is called. A parameter list can contain at most 8 parameters.
parameter_list
'('
indent open_parameter outdent ')'
space '('
closed_parameter_list ')'
open_parameter name open_parameter_ensue
open_parameter_ensue
"..."
space '|'
space expression more_open_parameters
more_open_parameters
more_open_parameters
""
linebreak open_parameter
closed_parameter_list
""
closed_parameter
closed_parameter name closed_parameter_ensue
closed_parameter_ensue
"..."
space '|'
space expression more_closed_parameters
more_closed_parameters
more_closed_parameters
""
','
space closed_parameter
A parameter list is a list of zero or more names, separated
by commas or stacked vertically. When the function is invoked, the argument values will be
assigned to these names and made available to the body. The
body can not modify the parameters. Parameters behave as def
, not var
. The number of
names in the parameter list determines the function's arity. If the function is invoked with too many arguments, it fails. The function's failure
handler will not get an opportunity to
handle the fail because the fail happens before the function
gets called.
If the function is invoked with too few parameters, the missing values will get null
. The |
default operator is allowed in parameter lists, replacing null
arguments with values.
Variadic functions (having a variable number of arguments) can be realized with the ...
ellipsis operator. It says to take all of the remaining arguments (possibly none) and put them in a new array that is bound to the last parameter in the parameter list.
And then, the body. There are two forms: the expression form wrapped in parens, and the statement form wrapped in braces.
body
'('
expression ')'
'{'
indent statements precondition postcondition failure outdent '}'
precondition
""
outdent "precondition"
indent requirement
postcondition
""
outdent "postcondition"
indent requirement
requirement expression more_requirements
more_requirements
""
linebreak requirement
failure
""
outdent "failure"
indent statements
The expression form is a single expression. The result of the expression is the return value.
The statement form includes a string of statements. The return
statement gives the return value. Implicit return (falling through the bottom) is not allowed.
The statement form can also include preconditions, postconditions, and a failure handler.
A statement form function can optionally contain preconditions and postconditions.
These are lists of requirements that must be true for the function to behave correctly. This supports a model of development called Design by Contract. The verifiable requirements can be much more effective than typechecking in discovering interface confusions and other bugs.
A requirement list is a list of logical expressions. All of the expressions must yield true
. The expressions must be pure, causing no side effects. In mature systems, there might be situations in which the checking of requirements is suppressed in order to improve performance.
The preconditions are evaluated after the arguments have been bound to arguments and default substitutions made and before the first statement is executed.
The postconditions are evaluated after the return
, but before control goes to the caller. In the case of a tail call, the postconditions are evaluated after the arguments are evaluated, but before the jump. Postconditions have access to the old
function. The old
function takes a variable name, returning the value of the variable before the first statement began execution. Postconditions are not evaluated if the function fails.
The context of a function is the function in which it is made. An outer function provides the context for an inner function. The inner function has access to all of the outer function's variables, definitions, and parameters. An inner function may not redefine names that are already defined in the context. The context (or lexical closure) remains available to the inner function even after the outer function has finished its invocation and returned.
Functions are invoked with the ()
paren suffix operator.
function_value
(
arguments)
The parens can contain zero or more expressions whose
values will be passed to the function as parameters. Each value will be
assigned to a named parameter. Parameters that do not have values will
be initialized to null
. If there are too many parameters,
then it fails
call my_function() # function invocation
Failures are interruptions to the normal flow of a program. They can be viewed in two parts: the fail, which interrupts the current operation, and the handling of the fail. Failures should not be used for ordinary outcomes. Failures should only be used for emergencies or unexpected situations.
fail
StatementThe fail
statement is like the return
statement
in that it stops the processing of the current function. However, instead of
transferring control back to the calling function, control goes to a failure
handler. If there isn't ultimately a failure handler, then the actor fails.
failure
HandlerA function can have an optional failure handler.
ƒ
parameter_list{
statements
failure
statements
}
If a function does not have an failure handler, it is given one by default that acts like
failure fail
When a fail
statement is executed in the main part of the
function body, control goes to the statements of the failure handler. The failure
handler must either return
or fail
. If it returns,
then the failure situation is over and control returns to the calling
function. If the failure handler fails, then execution of the current function
is abandoned and control passes to the failure handler of the caller.
If there is no caller, then the actor fails.
A failure handler may not contain an inner function, but it may call another function that has an inner function.
Example:
failure return plan_R()
If a function returns the result of calling a function, then a
tail call optimization may be made. Optimization will be performed on functions that contain explicit failure
handlers.
If a function returns the result of calling itself, and if the function
does not produce inner functions, then the t
optimization
will be performed. This converts the call into a jump, which can have
a big improvement in the performance of recursive functions.
def ƒ
factorial(n, s | 1) (
n > 1
then factorial(n - 1, s * n)
else s
)
factorial(5) # the result is 120
This makes continuation passing style possible.
A function can be used as a proxy for a record. If the function is called as
function
.
name(
argument0,
argument1)
then the function will be called as though it had been written as
function
("
name",
[
argument0,
argument1])
The function will be called with the name of the method and an array of arguments. The result that is returned by the function will be given to the caller.
Example:
def my_record: {
a: ƒ (value) (
"a-" ~ value
)
}
def ƒ
pseudorecord (
name
arguments
) {
if function?(my_record[name])
return my_record[name](arguments...)
call log("something is wrong with" ≈ name)
fail
}
function?(pseudorecord) # true
stone?(pseudorecord) # true
record?(pseudorecord) # false
length(pseudorecord) # 0
arity(pseudorecord) # 2
pseudorecord.a("ok") # "a-ok"