The Optional Parameters mechanism allows a flexible, uniform, self-documenting and efficient way of passing optional parameters to functions, procedures or constructors. The syntax is as follows, the optional parameters are separated from the rest of the parameters by a semicolon
SomeProc := proc( parm1:type1, ... ; opt1, opt2, ... ) .... end:
The parameters defined before the semicolon are the regular parameter, and their behaviour is as usual, except for the fact that when the procedure is invoked, all the regular parameters must be present. I.e. in the example below, f has to be called with at least one parameter (a set). The parameters defined after the semicolon are the optional parameters. Two examples are given below:
f := proc( a:set ; b:posint, (c=''):string ) ... end:
g := proc( ; 'mode'=(m:string), d:anything ) ... end:
The definition of an optional parameter is as follows (when ambiguous, "actual parameter" stands for the use of a parameter in a function call, "formal parameter" stands for the definition of a parameter in the proc statement):
| (1) | Each optional parameter definition is a type definition |
| (2) | The definition or exactly one of the subexpressions in each definition must be a "colon" expression, e.g. b:posint in f, m:string in g. For type-matching purposes, a colon expression matches the type defined on its right part. E.g. b:posint matches a posint. |
| (3) | The left part of a colon expression establishes the name of the variable that will hold the (part of) the parameter. It has two possible formats: name:type or (name=value):type |
| (4) | The name specified in the left part of a colon expression is the name of a local variable inside the function/procedure that will hold the value of what matches on the right part of the colon expression. E.g. f({5},ACGT,7) will result in the local variable b assigned 7 and the local variable c assigned ACGT. |
| (5) | If the left part of the colon expression is of the type name=value, then if no parameters match the optional parameter, the given name will be assigned "value". This is the preferred mechanism to define default values for unspecified parameters. E.g. f({0},3) will result in b assigned 3 and c assigned '', the empty string. |
| (6) | On calling a function/procedure with optional parameters, each actual optional parameter is paired against the first formal parameter that matches its type. The actual parameters are matched from left to right. E.g. g(mode=exact) will assign "exact" to the local variable m, g([1]) will assign [1] to the local variable d. |
| (7) | Once that a formal parameter has been matched with an actual parameter, its associated name is assigned, and this formal parameter cannot be paired with any other actual parameter. E.g. f({1},2,3) will assign 2 to b, and then will give an error, since 3 cannot be matched against any formal parameter (not yet matched). Notice that the number of actual parameters cannot be larger than then number of formal parameters when optional parameters are used. |
| (8) | Once that all the actual parameters are paired, any remaining formal parameters which are not paired yet and have a colon expression of the form (name=value):type will have their corresponding local variables assigned their default values. E.g. f({3}) will leave b unassigned and assign '' to c. |
The following are some worked examples relating to some known functions or common situations
| (I) | Align := proc( s1:string, s2:string ; (dm=DM):{Dayhoff,list(Dayhoff)}, ... ) |
| The function Align always requires two sequences which are strings. Those will be required on each use and will be s1 and s2. The first optional argument is a Dayhoff or list of Dayhoff matrices. If none is supplied, the function will have dm assigned the variable DM (which is normally assigned to the default Dayhoff matrix). | |
| (II) | Align := proc( ..., (Method='Local'):{'Local','Global','CFE','Shake'} ... |
| The next optional argument defines the method to be used. The method can be given as a name/string. Only 4 strings are valid methods, and if none is provided, Method is assigned 'Local'. This also resolves the problem of incorrectly specifying more than one method, once that the formal parameter is matched, it cannot be matched again. | |
| (III) | SomeClass := proc( ...., Comment:string ) |
| By having an optional parameter at the end of the parameter list, so that it catches any leftover string is a good way to allow optional informational data like comments. | |
| (IV) | Entry := proc( e:posint ; (db=DB):database ) |
| For system-wide variables, like DB, the default database, which are 99% of the time used from their default values, this definition provides the added flexibility that it does not require anything when the default is used, and if a database is passed as an argument, then it will be used correctly (without any extra work inside the function). | |
| (V) | DrawTree := proc( ..., 'LengthFormat' = (lf='%d'):{string,procedure} |
| DrawTree is a function which has many many options, most of which have practical defaults. In this case, the format for displaying branch lengths is by default an integer. It could be some other printf format (which is a string) or some procedure which produces the display. The internal variable lf is assigned the right information, all error checking and defaults are done automatically. No disassembling of the parameter is needed. |
Finally here is a more formal definition of the steps followed by the evaluation of parameters in the presence of optional parameters:
| (i) | The regular parameters (must all be there) are assigned and type-checked if they have a type definition. |
| (ii) | Each actual optional parameter (from left to right) is matched against the unmatched formal parameters (from left to right). An unmatched actual optional parameter gives an error. |
| (iii) | The unmatched formal parameters that have a default value definition are evaluated and the corresponding local variables assigned. This evaluation is done with access to all regular parameters and optional parameters already assigned. |
| (iv) | For further clarity, the types are never evaluated, they are as given in the proc statement, only the "value" part of a (name=vale):type is evaluated in full. |