A function is a parameterized expression or 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 processes. 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 that takes a parameter list and a body and produces a function object.
function_literal
'ƒ'
space optional_function_name parameter_list space body
optional_function_name
""
name
A function may be given a name for recursion and documentation.
A function literal may not be placed in a do
loop.
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 is a def
that is initialized by the caller. A parameter list can contain at most 4 parameters. If you need more than 4, consider using a single record or array instead.
parameter_list
'('
parameter_list_filler ')'
parameter_list_filler
""
parameter more_closed_parameters
indent parameter more_open_parameters outdent
parameter name optional_default
optional_default
""
space '|'
space expression
more_closed_parameters
""
','
space parameter more_closed_parameters
more_open_parameters
""
linebreak parameter more_open_parameters
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, the excess arguments are ignored.
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
paren_expression
'{'
statements precondition postcondition disruption '}'
precondition
""
"precondition"
indent requirement more_requirements outdent
postcondition
""
"precondition"
indent requirement more_requirements outdent
requirement expression
more_requirements
""
linebreak requirement more_requirements
disruption
""
"disruption"
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 disruption part.
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
record. The old
record contains the values of all of the variables and parameters at the time the function began execution.
Postconditions are not evaluated if the function disrupts.
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.The context (or lexical closure)
remains available to the inner function even after the outer function
has finished its invocation and returned. If an inner function redefines names that are already defined in the context (parameters, def
, or let
), it is not able to access the outer names.
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 arguments,
then the excess arguments are ignored, but they are still evaluated.
call my_function(do, re, mi) # function invocation
intrinsic
module
outer function
inner function
A function creates as scope. A scope is a set of names belonging to the particular invocation of a function. The names are declared by
def
statementuse
statementvar
statementA name in a function can only be declared once, and it must be declared before it is used.
In addition to the declared names, a function also has access to the names in the scopes of all of the functions it is nested in,
including the misty module and the intrinsics.
A function may use those outer names as if they were its own without declaration.
Names that were declared with var
may be set
.
A function may continue to enjoy access to the scope of an outer function even after the outer function has returned.
A function is not required to use any of the outer names. A function is free to reuse any of those outer names by declaration, but in doing so, it loses the right to use the outer version.
An inner function can see the scope of an outer function, but an outer function may not see the scope of an inner function.
Disrupts are interruptions to the normal flow of a program. They can be viewed in two parts: the disrupt, which interrupts the current operation, and the handling of the disruption. Disrupts should not be used for ordinary outcomes. Disrupts should only be used for emergencies or unexpected situations.
disrupt
StatementThe disrupt
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
disruption
part. If there isn't ultimately a disruption
part, then the process halts.
It is sometimes necessary to log
an explanation before a disrupt
.
disruption
A function can have an optional disruption
part.
ƒ (parameter_list) { statements disruption statements }
If a function does not have a disruption
part, it is given one by
default that acts like
disruption disrupt
When a disrupt
statement is executed in the main part of the
function body, control goes to the statements of the disruption
part. The
disruption
part must either return
or disrupt
. If it returns,
then the disrupt situation is over and control returns to the calling
function. If the disruption disrupts, then execution of the current function
is abandoned and control passes to the disruption part of the caller.
If there is no caller, then the process disrupts.
A disruption
part may not contain an inner function, but it may call another function that has an inner function.
Example:
disruption return plan_R()
If a function returns the calling of a function,
return a_function()
then an optimization may be made that converts the call into a parameterized jump. This enables an interesting set of recursive algorithms, avoiding memory exhaustion. This makes continuation passing style possible.
def factorial: ƒ factorial(integer, step | 1) ( integer > 1 then factorial(integer - 1, step * integer) # This updates integer and step and loops else step ) factorial(5) # the result is 120
The optimization will not be performed on functions that contain explicit disruption
parts or inner functions.
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 apply(my_record[name], arguments)
fi
log error: "something is wrong with" ≈ name
disrupt
}
function?(pseudorecord) # true
stone?(pseudorecord) # true
record?(pseudorecord) # false
length(pseudorecord) # 0
arity(pseudorecord) # 2
pseudorecord.a("ok") # "a-ok"
pseudorecord.b("ok") # disrupt
functino
"[]"
infix_operator
The operators can be used as functions by prefixing the operators with the ƒ
florin operator. These functions are called functinos.
So ƒ+
is the binary add function that can be passed to the reduce
function to make sums.
Examples:
ƒ/\(3, 4) # null and ƒ\/(3, 4) # null or ƒ|(3, 4) # 3 default ƒ=(3, 4) # false equal ƒ<>(3, 4) # true not equal ƒ<(3, 4) # true less ƒ<=(3, 4) # true less or equal ƒ>(3, 4) # false greater ƒ>=(3, 4) # false greater or equal ƒ~(3, 4) # "34" concat ƒ≈(3, 4) # "3 4" concat with space ƒ+(3, 4) # 7 add ƒ-(3, 4) # -1 subtract ƒ*(3, 4) # 12 multiply ƒ/(3, 4) # 0.75 divide ƒ÷(3, 4) # 0 integer divide ƒ[](3, 4) # null get
Three of the functinos are short circuiting in their operator form, but not in their functino form: ƒ/\
and ƒ\/
or ƒ|
default.
The equal functino can take two or three arguments. The first two arguments are values to be compared.
If all of the parameters are numbers, an optional tolerance argument may be included which must be a non-negative number.
The result is true
if the absolute value of the difference between the
two numbers is less than or equal to the tolerance.
If the arguments are texts, and if the tolerance is true
, then the comparison is case-insensitive.
Examples:
def first: 12.3775 def second: 12.38 set exactly: ƒ=(first, second) # exactly is false set sloppy: ƒ=(first, second, 0.01) # sloppy is true def first: "vorpal" def second: "VORPAL" set exactly: ƒ=(first, second) # exactly is false set sloppy: ƒ=(first, second, true) # sloppy is true
The not equal functino can take two or three arguments. The first two arguments are values to be compared. The optional third argument is a tolerance. When comparing numbers, the tolerance is a number that indicates the allowable difference. When comparing texts, the tolerance is a logical that if true
ignores case.
The get functino takes two arguments. The first argument is an array, record, or text. The second argument is an element number or key. It returns an element, a property, or a character, or null
.
These builtin functions take a callback argument:
@.new
@.contact
@.connection
A callback function has this signature:
ƒ (value, reason)
The value is the value of the request if it was successful. The value is null if it was unsuccessful.
If the value is null
, then the reason may include an error message or indication of the cause of the request.