Each function definition includes zero or more formal parameters, enclosed within ()'s. Each formal looks like:
type name = expressiontype and = expression are optional. (formal's have one other form, ``...", discussed in § 6.4.4, page
name serves
as the name of a local variable that during a function call is initialized
with the corresponding actual argument. (See § 6.6.1, page , for a discussion
of local variables.)
As in most programming languages,
actual arguments are match with formal parameters left-to-right:
function diff(a, b) a-bmatches 3 with a and 7 with b. Argument matching can also be done ``by name":
![]()
diff(3, 7)
diff(b=1, a=2)matches 1 with b and 2 with a.
If in the function definition a formal includes = expression then when calling the function an actual argument for that formal can be left out, and the formal will instead be initialized using expression. expression is referred to as the formal's default. As we saw above, we could define diff as:
function diff(a, b=1) a-bin which case a call with only one argument would match that argument with a and initialize b to 1. A call using by-name argument matching, though, could not specify b and not a, since a has no default:
diff(b = 3)is illegal.
We could instead have defined diff with:
function diff(a=0, b) a-bin which case when only b is specified in a call diff becomes the ``negation" function. A call like:
diff(6)is now illegal, since 6 matches a and not b; but the call
diff(b = 6)is legal and returns -6. Arguments which have defaults can also simply be left out, as long as their absence is denoted. The previous invocation could also be written as:
diff(,6)Since the first argument is purposefully left out, it gets assigned the default value. You can test for missing arguments using the missing() function (§ 6.5, page
Note that while match-by-position and match-by-name arguments can be intermixed, a parameter must only be specified once. For example,
diff(3, 4, a=2)is illegal because a is matched twice, first to 3 and then to 2. Furthermore, once a match-by-name argument is given no more match-by-position arguments can be given, since their position is indeterminate:
diff(a = 3, 2)is illegal, since it's unclear what parameter 2 is meant to match.
A formal parameter definition can also include a type. Presently, the type is one of ref, const, or val. The type indicates the relationship between the actual argument and the formal parameter.
If the formal parameter's type is ref
then the formal is initialized as a ref reference to the actual
argument, and can be used to change its value if the actual argument
is a variable or record field (via a val assignment; see
§ 4.6, page ). See § 3.8, page
, for a full discussion of
references.
The val parameter type indicates that the parameter can be modified, but changes won't be reflected in the actual parameter which is passed into the the function. The default type for parameters is val.
If the type is const, then it's initialized as
a const value. A const parameter cannot be modified in the
course of executing the function. Attempts to modify const parameters
result in errors (see § 3.9, page for a discussion of const
values).
Here is an example of a function with a ref parameter that increments its argument:
function bump(ref x) { val x +:= 1 }After executing:
y := 3 bump(y)y's value is 4. Note though that the following call:
bump(3)is perfectly legal and does not change the value of the constant 3 to 4!
Here's another example of using a ref parameter:
# sets any elements of x > a to 0. func remove_outliers(ref x, a) { x[x > a] := 0 }
For the most part, the person writing a function does not have to worry
about parameter passing efficiency. This is a result of the fact that all
values in Glish are stored so that access is done using copy-on-write
(see § 3.10, page ). Copy-on-write means that if two values are assigned
to be equal then they will share the same underlying storage until one is modified.
This same mechanism is used when passing parameters or returning values. The default parameter type is val, but the parameter is only really copied if the parameter is modified in the function. So large parameters are only copied if necessary. Using a const parameter type provides a way to ensure that a given parameter does not change and, as a result, is not copied. With return values, the value is never modified as in the function block after the return statement, and as a result, return values will not be copied in the process of returning from the function.
In the future Glish will support more explicit typing of parameters. For example, it will be possible to define a function like:
function abs(val numeric x)in which case if abs is called with a non-numeric value Glish will detect the type clash and generate an error.
You can write functions that take a variable number of parameters by including the special parameter ``..." (called ellipsis) in the function definition. For example, here's a function that returns the sum of all its arguments, regardless how many there are:
func total(...) { local result := 0 for ( i in 1:num_args(...) ) result +:= nth_arg(i, ...) return result }
Two functions are available for dealing with variable argument lists.
num_args returns the number of arguments with which it was called, and nth_arg returns a copy of the argument specified by its first argument, with the first argument numbered as 0. For example,
num_args(6,2,7)returns 3 and
nth_arg(3, "hi", 1.023, 42, "and more")returns 42.
There's a temptation to expect num_args and nth_arg to return information about ``..." if they're not given an argument list, but presently they do not. Probably they will be changed to do so in the future.
Note that the only operation allowed with ``..." is to pass it as an argument to another function. It cannot otherwise appear in an expression. When passing it to a function, it is expanded into a list of const references to the actual arguments matched by the ellipsis. For example,
func many_min(x, ...) { if ( num_args(...) == 0 ) return x else { ellipsis_min := many_min(...) if ( ellipsis_min < x ) return ellipsis_min else return x } }returns the minimum of an arbitrary number of arguments.
When an ellipsis is used in a function definition then any parameters listed after it must be matched by name (or by default). Furthermore, the corresponding arguments must come after those to be matched by the ellipsis. For example, given:
func dump_ellipsis(x, ..., y) { for ( i in num_args(...) ) print i, nth_arg(i,...) }both of the following calls are illegal:
dump_ellipsis(1, 2, 3) dump_ellipsis(1, y=2, 3)In the first y is not matched, and in the second the actual argument 3 is not matched (in particular, it is not matched by the ellipsis). The following, though, is legal:
dump_ellipsis(1, 2, y=3)and results in the ellipsis matching the single argument 2.
An ellipsis can also have a default value specified. This value will be used as the value for any arguments which are purposefully left out. In the following,
func add(...=0) { local ret := 0; for ( i in num_args(...) ) ret +:= nth_arg(i,...) }add is defined so that any arguments which are purposefully left out will be set to zero. So the following invocation,
print add(1,2,,,5)prints 8. The two arguments between the 2 and the 5 default to 0.
An ellipsis can be used to construct a vector. This allows all of the parameters to be captured as a vector:
func args(...) { return [...] }this will return the parameters as a vector. So given the following invocations,
args(1,5,8) args(4,3:5,1)the result of the first is [1, 5, 8], and the result of the second is [4, 3, 4, 5, 1]. It is important to note, however, that if one of the arguments is not an array value, e.g. a record, an error results.