Contents Previous Next Index
8 Programming with Modules
This chapter describes the structure and flexibility of Maple modules.
Modules allow you to associate related procedures and data in one structure. By creating a module, you can write code that can be reused, transported, and easily maintained. You can also use modules to implement objects in Maple.
This chapter provides several example modules, many of which are available as Maple source code in the samples directory of your Maple installation. You can load these examples into the Maple library to modify and extend them, and use them in custom programs.
8.1 In This Chapter
Syntax and semantics
Using modules as records or structures
Modules and use statements
Interfaces and implementations
8.2 Introduction
You may decide to create a module for one of the purposes described below.
Encapsulation
Encapsulation is the act of grouping code together in one structure to separate its interface from its implementation. By doing so, you can create applications that are transportable and reusable and that offer well-defined user interfaces. This makes your code easier to maintain and understand--important properties for large software systems.
Creating a Custom Maple Package
A package is a means of bundling a collection of Maple procedures related to a domain. Most of the Maple library functionality is available in packages.
Creating Objects
Objects can be represented using modules. In software engineering or object-oriented programming, an object is defined as an element that has both a state and behavior. Objects are passed the same way as ordinary expressions, but also provide methods which define their properties.
Creating Generic Programs
Generic programs accept objects with specific properties or behaviors. The underlying representation of the object is transparent to generic programs. For example, a generic geometry program can accept any object that exports an area method, in addition to other objects. The framework of the program would rely on information in each given object to determine specific behaviors, while the overall program implements a common pattern between the objects.
8.3 A Simple Example
In the following example, a module generates a sequence of numbers.
Counter := module() description "number generator"; export getNext; local count; count := 0; getNext := proc() count := 1 + count; end proc; end module: Counter:-getNext(); Counter:-getNext(); Counter:-getNext();
1
2
3
The module definition format, which will be described in more detail in the next section, is similar to a procedure definition in that the body is contained within a delimited code block. Also, elements such as local variables, options, and description are declared at the top of the module. Unlike a procedure, the body of the module is evaluated only once when it is declared. The values that are defined during this evaluation process, and the values that are defined in subsequent usage of the module, are stored and can be used again.
In a module definition, you can declare exported variables, which are names that will be made available once the module has been run. These exported variables can be accessed by using the member selection operator (:-) or the indexing operation ( [] ) , while local variables remain private (that is, they are accessible only by methods within the module). The example above declares and uses one exported local variable called getNext and one local variable called count.
8.4 Syntax and Semantics
Module definitions have the following general syntax.
module() local L; export E; global G; options O; description D; B end module
The Module Definition
All module definitions begin with the keyword module, followed by an empty pair of parentheses. This is similar to the parentheses that follow the proc keyword in a procedure definition. Following that is an optional declaration section and the module body. The keywords end module (or simply end) terminate a module definition.
The simplest valid module definition is
module() end;
module...end module
which does not contain exported variables, local variables, references, global variables, or a body of statements.
The Module Body
The body of a module definition contains the following components:
Zero or more Maple statements. The body is executed when the module definition is evaluated, producing a module.
Assignment statements that assign values to the exported names of the module.
Also, the body can optionally contain the following components:
Assignments to local variables and arbitrary computations.
A return statement, which cannot contain a break or next statement outside of a loop. Running a return statement terminates the execution of the body of the module definition.
Module Parameters
Unlike procedures, module definitions do not have explicit parameters because modules are not called (or invoked) with arguments.
Implicit Parameters
All module definitions have an implicit parameter called thismodule. Within the body of a module definition, this special name evaluates to the module in which it occurs. You can, therefore, refer to a module within its own definition before the result of evaluating it has been assigned to a name.
thismodule is similar to thisproc in procedures, but is not the same as procname. The difference between thismodule and procname is that procname evaluates to a name, while thismodule evaluates to the module expression itself. There is no concept of a modulename implicit variable because the invocation phase of evaluating a module definition is part of its normal evaluation process, and it occurs immediately. Procedures, on the other hand, are not invoked until they are called with arguments. Normally, at least one name for a procedure is known by the time it is called; this is not the case for modules.
Implicit parameters related to passing arguments (for example, _params, _options, _passed, and others) cannot be referenced in module definitions. They are only available within the scope of a procedure.
For more information on procedures, see Procedures.
Named Modules
In a module definition, an optional symbol can be specified after the module keyword. Modules created in this way are called named modules.
Semantically, named modules are almost identical to normal modules, but the exported variables of named modules are printed differently, allowing the module from which it was exported to be identified visually. In the following example, a normal module is assigned to the name NormalModule.
NormalModule := module() export e; end module; NormalModule:-e;
NormalModule≔module...end module
e
In the following example, the symbol (the name of the module) after the module keyword is NamedModule.
module NamedModule() export e; end module;
moduleNamedModuleexporte;end module
NamedModule:-e;
NamedModule:−e
When the definition of a named module is evaluated, the name (which appears immediately after the module keyword) is assigned the module as its value and the name is protected (that is, it cannot be modified). Therefore, a named module is usually created only once. For example, an error occurs when the same named module definition above is executed.
Error, (in NamedModule) attempting to assign to `NamedModule` which is protected. Try declaring `local NamedModule`; see ?protect for details.
Executing the normal module definition again creates a new instance of the module and does not result in an error. It simply reassigns the variable NormalModule to the new module instance.
NormalModule := module() export e; end module;
If you save a normal module to a Maple library archive, which is a file used to store a collection of internal files, the normal module becomes a named module the next time it is loaded from the library archive. The savelib command, which is the command used to save a file to a library archive, takes the name of the variable assigned a module, and saving the file associates this name with the module.
For more information about library archive files, see Writing Packages.
Important: Do not assign a named module to another variable, for example,
SomeName := eval( NamedModule );
SomeName≔moduleNamedModuleexporte;end module
SomeName:-e;
Exports of named modules are printed using the distinguished name that was given to the module when it was created, regardless of whether it has been assigned to another name.
Whether a module has a name also affects the reporting of errors that occur during its evaluation. When the second attempt to evaluate the named module definition above generated an error, the error message reported the location of the error by name. In contrast, when an error occurs during the evaluation of a normal module definition, the name unknown is used instead.
NormalModule := module() export e; error "oops"; end module;
Error, (in anonymous module) oops
This process differs from procedure error reporting. Maple cannot report the name of a normal module (that is, the name of the variable to which the module is assigned) because the evaluation of the right-hand side of an assignment occurs before the assignment to the name takes place. Therefore, the error occurs before the association between a variable and the module has occurred.
Declarations
The declarations section of the module must appear immediately after the parentheses. All of the statements in the declarations section are optional, but, at most, one of each kind can be specified. Most module declarations are the same as those for procedures.
For more information, see Parameter Declarations.
Description Strings
You can provide a brief description to summarize the purpose and function of your modules. Providing a description is valuable to other users who read your code. Include text after the description keyword as you would in a procedure definition.
Hello := module() description "my first module"; export say; say := proc() print( "HELLO WORLD" ) end proc; end module:
When the module is printed, its description string is displayed.
eval( Hello );
The export declaration is described later in this chapter.
Global Variables
Global variables referenced in a module definition are declared by using the global keyword. Following the global keyword is a sequence of one or more symbols, which are associated with their global module instances. In certain cases, you must declare a name as a global variable to prevent implicit scoping rules from making it local.
Hello := module() export say; global message; say := proc() message := "HELLO WORLD!" end proc; end module:
message;
message
Hello:-say();
HELLO WORLD!
Local Variables
You can define variables that are local to the module definition by using the local declaration. Its format is the same as for procedures. The following example is a variant of the previous Hello module, which uses a local variable.
Hello := module() local loc; export say; loc := "HELLO WORLD!"; say := proc() print( loc ) end proc; end module:
Local variables (or locals) cannot be used or changed outside of the module definition in which they occur. In other words, they are private to the module.
A local variable in a module is a distinct object from a global variable with the same name. While local variables in procedures are typically used only for the duration of the execution time of the procedure body, module local variables are stored after the module definition is executed. They can be used to maintain a state. For example, in the Counter example described at the beginning of this chapter, a local count variable stores the current value of the counter. The count local variable increments each time the getNext procedure is invoked. Its new value is stored and can be used the next time the procedure is called. At the same time, because count is local, no external programs can change its value and end the sequence defined by the module.
Exported Local Variables
Procedures and modules both support local variables. However, only modules support exported local variables, which are often called exports.
Module exports are declared by using the export declaration. It begins with the keyword export, followed by a (nonempty) sequence of symbols. A name is never exported implicitly; exports must be declared.
The result of evaluating a module definition is a module. You can view a module as a collection of its exports, which are also referred to as members of the module. These are simply names that can (but need not) be assigned values. You can establish initial values for the exports by assigning values to them in the body of the module definition.
The word export is a short form for exported local variable. In most cases, a module export is a local variable such as those declared with the local declaration. The difference is that you can access the exported local variables of a module after it has been created.
To access an export of a module, use the member selection operator (:-). Its general syntax is
modexpr :- membername
modexpr must be an expression that evaluates to a module and membername must be the name of an export of the module to which modexpr evaluates. Anything else signals an exception. You cannot access the local variables of an instantiated module by using this syntax.
The Hello example above has one export named say. In the following example, say is assigned a procedure. To call it, enter
The following expression raises an exception because the name noSuchModule is not assigned a module expression.
noSuchModule:-e;
Error, `noSuchModule` does not evaluate to a module
In the following example, a module expression is assigned to the name m and the member selection expression m:-e evaluates to the value of the exported variable e of m.
m := module() export e; e := 2 end module: m:-e;
Since m does not export a variable named noSuchExport, the following expression raises an exception.
m:-noSuchExport;
Error, module does not export `noSuchExport`
In addition to the :- syntax, square brackets can also be used to reference a module export.
m[e];
The square bracket notation has different evaluation rules than member selection. When using the member selection operator (:-), the export name must be known in advance. When using [], the name of the export can be computed. In this example, an exported variables value can be selected from an arbitrary module.
m := module() export a := 1, b := 2, c := 3; end module: FirstExport := proc( m::`module` ) local ex := exports(m); return m[ex[1]]; end proc; FirstExport(m);
FirstExport≔procm::modulelocalex;ex ≔ exports⁡m;returnm[ex[1]]end proc
Important: Exports do not need to have assigned values. The following module exports an unassigned name. This illustrates the importance of distinguishing module exports from global variables.
m := module() export e; end module:
References to the exported name e in m evaluate to the name e.
m:-e;
Note, however, that this is a local name e and not the global instance of the name.
evalb( e = m:-e );
false
The first e in the previous expression refers to the global e, while the expression m:-e evaluates to the e that is local to the module m. This distinction between a global and export of the same name is useful. For example, you can create a module with an export sin. Assigning a value to the export sin does not affect the protected global name sin.
Determining the Export Names
You can determine the names of the exports of a module by using the exports procedure.
exports( Hello );
say
exports( NormalModule );
This procedure returns the global instances of the export names.
exports( m );
evalb( (21) = e );
true
You can also obtain the local instances of those names by using the option instance.
exports( m, 'instance' );
evalb( (23) = e );
evalb( (23) = m:-e );
You cannot have the same name declared as both a local variable and an exported local variable.
module() export e; local e; end module;
Error, (in anonymous module) declaration of local variable `e` masks an earlier exported declaration
(The declared exports and locals actually form a partition of the names that are local to a module.)
Testing for Membership in a Module
As described in previous chapters, the member command can be used to test whether a value is a member of a set or list.
member( 4, { 1, 2, 3 } );
This command can also be used for membership tests in modules.
member( say, Hello );
member( cry, Hello );
The first argument is a global name whose membership is to be tested, and the second argument is the name of a module. The member command returns the value true if the module has an export whose name is the same as the first argument.
The member command also has a three-argument form that can be used with lists to determine the first position at which an item occurs.
member( b, [ a, b, c ], 'pos' );
The name pos is now assigned the value 2 because b occurs at the second position of the list. [ a, b, c].
pos;
When used with modules, the third argument is assigned the local instance of the name whose membership is being tested, provided that the return value is true.
member( say, Hello, 'which' );
which;
eval( which );
procprint⁡locend proc
If the return value from the member command is false, the name remains unassigned or maintains its previously assigned value.
unassign( 'which' ):
member( cry, Hello, 'which' );
which
Module Options
Similar to procedures, a module definition can contain options. The options available for modules are different from those for procedures. Only the options trace and copyright are common to both procedures and modules. The following four options have a predefined meaning for modules: load, unload, package, and record. The load and unload options cover functionality defined by the ModuleLoad and ModuleUnload special exports described in the next section.
For more information, refer to the module,option help page.
The package Option
A package is a collection of procedures and other data that can be treated as a whole. Packages typically gather several procedures that allow you to perform computations in a well-defined problem domain. Packages can contain data other than procedures and can even contain other packages (subpackages).
The package option is used to designate a module as a Maple package. The exports of a module created with the package option are automatically protected.
For more information, see Writing Packages.
The record Option
The record option is used to identify records, which are fixed-size collections of items. Records are created by using the Record constructor and are represented using modules.
For more information, see Records.
Special Exports
Certain specially named exports, when defined in a module, affect how modules behave in Maple. These special exports are described below. In most cases, they can be declared as either exported local variables or local variables.
The ModuleApply Procedure
When a procedure named ModuleApply is declared as an export or local of a module, the module name can be used as if it were a procedure name.
Consider the Counter example described at the beginning of this chapter. Since it only has one method, the calling sequence can be shortened by using the ModuleApply function.
Counter := module() export ModuleApply; local count; count := 0; ModuleApply := proc() count := 1 + count; end proc; end module: Counter(); Counter(); Counter();
In this example, calls to Counter:-ModuleApply() are not needed and the results are the same as those generated by the original Counter example. The ModuleApply function can specify and accept any number of parameters.
You can also use the ModuleApply function to create module factories, a standard object-oriented design pattern described later in this chapter.
The ModuleIterator Procedure
The ModuleIterator procedure defines how a module functions when it is used as the in clause of a for loop.
for e in myModule do # Do something with e end do; for i, e in myModule do # Do something with e, whose index in myModule is i. end do;
In the example below, the ModuleIterator procedure returns two procedures: hasNext and getNext. These procedures can have any names, and in fact, do not require names. When the ModuleIterator procedure is called, an iterator is initialized for the instance, the details of which are kept hidden from the caller. The two returned procedures can then be used to iterate over the instance to perform a specific task. For example, consider a class that implements a form of a set of which mySet is an instance. You can iterate over this set as follows (passing i is only necessary if the index is needed):
(hasNext,getNext) := ModuleIterator(mySet); while hasNext() do e := getNext('i'); # Do something with e, whose index is in i. end do;
The example above is an explicit use of the ModuleIterator procedure. However, this mechanism is also used implicitly by the Maple for-loop construct,
The hasNext procedure returns a value of true or false depending on whether remaining elements need to be processed. Successive calls to hasNext with no intervening calls to getNext return the same result. The getNext procedure returns the next element to process, and increments the iterator. If an argument was passed to getNext, it will be an unevaluated name, and getNext should assign to it an index corresponding to the iterator (one that has meaning to some other procedure in the module that can be used to look up an entry using this index). If the concept of an index does not make sense for this particular module, getNext should not perform the assignment. These procedures should be implemented so that it is always safe to call getNext after the most recent call to hasNext returns a value of true. The result of calling getNext after hasNext has returned a value of false, or before hasNext has ever been called, is up to the implementer of the class.
The following example implements a fixed-size container of a collection of strings. In a real-world use case, these might have been other values that are expensive to compute, so we allocate this module to collect them for convenient access.
CommonWords := module() export ModuleIterator, lookup; local words := [ "the", "be", "to", "of", "and" ]; ModuleIterator := proc() local i := 1; return ( # This is the (anonymous) hasNext procedure. proc() evalb( i <= numelems(words) ) end proc, # This is the getNext procedure. proc(returnIndex := NULL) local word := words[i]; # If a name was passed to hasNext, assign it the index. if returnIndex <> NULL then returnIndex := i end if; i := i + 1; word end proc ) end proc; # This procedure can use the indices returned by getNext. lookup := proc(index) words[index] end proc; end module;
CommonWords≔module...end module
This loop will enumerate only the words in the CommonWords module:
for e in CommonWords do e end do;
the
be
to
of
and
This loop will enumerate both the words and their indices:
for i, e in CommonWords do printf( "'%s' has index %d\n", e, i ); end do;
'the' has index 1 'be' has index 2 'to' has index 3 'of' has index 4 'and' has index 5
Notice that the ModuleIterator procedure declares a local variable, i, that is used as the index by hasNext and getNext. If this variable were declared local to the entire module, then it would only be possible to iterate over it once, as i would never get reset to 1. By declaring i local to ModuleIterator, it becomes part of the environment for the instances of hasNext and getNext returned by a particular call to ModuleIterator. Thus, multiple iterators into the same module can exist simultaneously without interfering with one another. For example:
for i, e in CommonWords do printf( "'%s' has index %d\n", e, i ); if i = 3 then for j, f in CommonWords do printf( "\t'%s' has index %d\n", f, j ) end do end if end do;
'the' has index 1 'be' has index 2 'to' has index 3 'the' has index 1 'be' has index 2 'to' has index 3 'of' has index 4 'and' has index 5 'of' has index 4 'and' has index 5
When the module iterator is used by the seq, add, or mul commands, Maple first checks if the module is an object that exports the numelems command. If so, it will call the numelems command to determine how large to preallocate the result, and the hasNext and getNext procedures must return exactly that many elements. If the module does not export a numelems method, Maple will enlarge the result as needed, which will consume more space (as intermediate results are discarded) and time (garbage collection), although Maple will try to do this as efficiently as possible.
The ModuleLoad Procedure
The ModuleLoad procedure is executed automatically when a module is loaded from the Maple library archive in which it has been saved. In a normal session, initialization code can be included in the module body. When loading a saved module, extra initialization code is sometimes required to set up run-time properties for the module. For example, a module that loads procedures from a dynamic-link library (.dll) file may need to call the define_external function during the initialization process. For more information on the define_external function, see Advanced Connectivity.
Consider the Counter example at the beginning of the chapter. The count index can have any value when it is saved. The next time you use it, you might want to reset the count to zero so that it is ready to start a new sequence. This can be done by using the ModuleLoad procedure.
Counter := module() export getNext, ModuleLoad; local count; ModuleLoad := proc() count := 0; end proc; ModuleLoad(); getNext := proc() count := 1 + count; end proc; end module: Counter:-getNext();
Note that the initialization code is contained within the ModuleLoad procedure. After that, the ModuleLoad procedure is also called. By defining the module in this way, you will get the same results when executing the module definition as you would when loading a saved module from a library archive.
The results of ModuleLoad can be duplicated using a procedure with a different name by using the load=pname option in the option sequence of the module.
ModulePrint
If a module has an export or local named ModulePrint, the result of the ModulePrint command is displayed instead of the module when a command in that module is executed.
The ModulePrint procedure does not display output. Instead, it returns a standard Maple expression that will be displayed. The expression returned can be customized to another object that portrays or summarizes the module.
In the following example, the Counter example will be extended from the ModuleIterator example to display a summary of what the module does.
Counter := module() export ModuleIterator, getNext, lower := 0, upper := 5; local ModulePrint, hasNext, count := 0; hasNext := proc() evalb( count < upper ); end proc; getNext := proc() count := 1 + count; return count ; end proc; ModuleIterator := proc() return hasNext, getNext; end proc; ModulePrint := proc() return [[ sprintf("Counter from %d to %d", lower, upper) ]]; end proc; end module;
Counter≔Counter from 0 to 5
ModuleUnload
The ModuleUnload procedure is called immediately before a module is discarded. A module is discarded either when it is no longer accessible and is garbage collected, or when you end your Maple session.
M := module() export ModuleUnload; ModuleUnload := proc() print("I am gone"); end proc; end module: unassign(M); 1;2;3;4; gc();
4
I am gone
You may not see the "I am gone" message after executing the code above because several factors determine exactly when memory is free to be garbage collected. At a minimum, no references can be left in the module. It must not be assigned or contained in any other live expression. This includes the ditto operators and the list of display reference handles (that is, the undo/redo buffer of the GUI). Also, it must not be identified as being alive by the garbage collector (i.e. a reference to the module is not found by the collector).
A module can become inaccessible, and therefore subject to garbage collection before the unload= procedure is executed, but can then become accessible again when that procedure is executed. In that case, the module is not garbage collected. When it eventually is garbage collected, or if you end your Maple session, the unload= procedure is not executed again.
The behavior of ModuleUnload can be duplicated using a procedure with a different name by using the unload=pname option in the option sequence of the module.
Implicit Scoping Rules
The bindings of names that appear within a module definition are determined when the module definition is simplified. Module definitions are subject to the same implicit scoping rules that apply to procedure definitions. Under no circumstances is a name ever implicitly determined to be exported by a module; implicitly scoped names can resolve only to non-exported local variables or global names.
Lexical Scoping Rules
Module definitions, along with procedure definitions, follow standard lexical scoping rules.
Modules can be nested, in the sense that a module can have any of its exports assigned to a module whose definition occurs within the body of the outer module.
Here is a simple example of a submodule.
m := module() export s; s := module() export e; e := proc() print( "HELLO WORLD!" ) end proc; end module end module:
The global name m is assigned a module that exports the name s. Within the body of m, the export s is assigned a module that exports the name e. As such, s is a submodule of m. The Shapes package, which is described in Writing Packages, illustrates the use of submodules.
Modules and procedures can both be nested to an arbitrary depth. The rules for the accessibility of local variables (including exported locals of modules) and procedure parameters are the same as the rules for nested procedures.
Module Factory
The Counter example used up to this point would be more useful if you could have many Counter modules running at the same time, and if they could be specialized according to specified bounds. Modules do not take explicit parameters, but you can write a generic module that could be specialized by using the factory design pattern.
To do this, write a constructor procedure for the module that accepts the lower and upper bound values as arguments. The following module creates a Counter.
MakeCounter := proc( lower::integer, upper::integer ) return module() export ModuleIterator, getNext; local ModulePrint, hasNext, count := lower; hasNext := proc() evalb( count < upper ); end proc; getNext := proc() count := 1 + count; return count ; end proc; ModuleIterator := proc() return hasNext, getNext; end proc; ModulePrint := proc() return [[ sprintf("Counter from %d to %d", lower, upper) ]]; end proc; end module; end proc; c1 := MakeCounter(6,10); c1:-getNext(); c1:-getNext(); c2 := MakeCounter(2,4); c2:-getNext(); c1:-getNext();
MakeCounter≔proclower::integer,upper::integerreturnmodulelocalModulePrint,hasNext,count;exportModuleIterator,getNext;count ≔ lower;hasNext ≔ procevalb⁡count<upperend proc;getNext ≔ proccount ≔ 1+count;returncountend proc;ModuleIterator ≔ procreturnhasNext,getNextend proc;ModulePrint ≔ procreturnsprintf⁡Counter from %d to %d,lower,upperend procend moduleend proc
c1≔Counter from 6 to 10
7
8
c2≔Counter from 2 to 4
9
In the above example, two specialized Counters operate at the same time with different internal states.
Modules and Types
Two Maple types are associated with modules. First, the name module is a type name. Naturally, an expression is of type module only if it is a module. When used as a type name, the name module must be enclosed in name quotes (`).
type( module() end module, '`module`' );
type( LinearAlgebra, '`module`' );
Second, a type called moduledefinition identifies expressions that are module definitions. In the previous example, the module definition
module() end module:
was evaluated before being passed to type, so the expression that was tested was not the definition, but the module to which it evaluates. You must use unevaluation quotes (') to delay the evaluation of a module definition.
type( 'module() end module', 'moduledefinition' );
Other important type tests satisfied by modules are the types atomic and last_name_eval.
type( module() end module, 'atomic' );
The procedure map has no effect on modules; modules passed as an argument to map remain unchanged.
map( print, module() export a, b, c; end module );
Modules also follow last name evaluation rules. For more information on last name evaluation rules, refer to the last_name_eval help page.
m := module() end module: m; type( m, 'last_name_eval' );
m
Although the type module is a surface type, which checks information at the top level of your code, it acts also as a structured type. Parameters passed as arguments to the unevaluated name module are interpreted as export names. For example, the module
m := module() export a, b; end module:
has the structured module type `module`( a, b ):
type( m, '`module`( a, b )' );
It also has the type `module`( a )
type( m, '`module`( a )' );
because any module that exports symbols a and b is a module that exports the symbol a.
For more information about structured types, refer to the type,structure help page.
8.5 Records
The Record command, which was introduced in Records, is an example of a module factory that can help you to write reusable code. Like an Array, a record is a fixed-size collection of items but, like a table, individual items stored within the record can be referenced by a name, rather than a numeric offset. In Maple, records, which are called structures in C++, are implemented as modules.
Creating Records
To create a record, use the Record constructor. In the simplest form, it takes the field names as arguments.
rec := Record( 'a', 'b', 'c' );
rec≔Record⁡a,b,c
The name rec is now assigned a record with fields named a, b, and c. You can access and assign values to these fields by using the expressions rec:-a, rec:-b, and rec:-c.
rec:-a := 2;
a≔2
rec:-a;
If unassigned, a record field evaluates to the local instance of the field name.
rec:-b;
b
evalb( (54) = b );
This is useful because the entire record can be passed as an aggregate data structure.
The record constructor accepts initializers for record fields. That is, you can specify an initial value for any field in a new or unassigned record by passing an equation with the field name on the left side and the initial value on the right.
r := Record( 'a' = 2, 'b' = sqrt( 3 ) );
r≔Record⁡a=2,b=3