Extension Mechanisms
|
Introduction
|
|
|
Many built-in and library procedures admit user-defined extensions that increase the knowledge of these routines. For example, new types and conversions can be added dynamically to the system, and the range of mathematical functions that can be handled by evalf and diff can be expanded beyond what is provided with the standard Maple library.
|
|
Each unique extension of a facility, such as diff or type, is associated with a name. What the name represents depends upon the facility being extended. In the case of type, it is the name of the type. In the case of diff, it is the name of the function to be differentiated.
|
|
There are two extension mechanisms in use: a classical extension mechanism used by most extensible facilities, and a modern extension mechanism, used by newer facilities that admit user extensions.
|
|
- The classical extension mechanism has a single, global namespace for extension names.
|
|
- The modern mechanism allows you to name extensions in multiple namespaces. (In other words, using the modern extension mechanism allows extensions to have names that are local to a procedure or module.)
|
|
|
Classical Extension Mechanism
|
|
|
All built-in procedures and most library procedures, which admit user extensions, make use of the classical extension mechanism, based on name concatenation. Briefly, an extensible procedure extendme is given new knowledge by defining an expression (most often a procedure) assigned to a name of the form `extendme/foo`, where foo is the name of the extension. For instance, a new type foo can be introduced by defining either a structured type, or a procedure implementing the desired type test, assigned to the name `type/foo`. The Maple floating-point evaluator can be taught to evaluate a function F numerically by defining an extension named `evalf/F`.
|
|
The classical extension mechanism for any given facility has a single, global namespace, because the name of the implementation of the extension is formed by concatenating two names. (For instance, `evalf/F` is formed as the concatenation of `evalf/` and F.) Therefore, it is not possible to define a local extension with a name of the form `extendme/foo`. Nor is it possible to implement an extension of extendme with two distinct extensions foo, at least one of which is (necessarily) local.
|
|
The specific rules that must be satisfied by an extension for any given facility extendme are documented in the help for that facility. For example, the specific rules that must be satisfied by an extension for the diff, type, expand, and evalf facilities are found in the diff, type, expand, and evalf,details help pages.
|
|
Example Using the `expand` Procedure
|
|
|
An example in which the expand procedure is extended with knowledge of a new mathematical function GroupOrder (representing the order of a finite group) is as follows. It is assumed here that the names DirectProduct and QuotientGroup are inert representations of these constructions.
|
>
|
`expand/GroupOrder` := proc( p )
if nargs = 1 and type( p, 'specfunc( anything, DirectProduct )' ) then
mul( GroupOrder( op( i, p ) ), i = 1 .. nops( p ) )
elif nargs = 1 and type( p, 'QuotientGroup( anything, anything )' ) then
GroupOrder( op( 1, p ) ) / GroupOrder( op( 2, p ) )
else
'GroupOrder'( args )
end if
end proc:
|
>
|
expand( GroupOrder( DirectProduct( A, B, C ) ) );
|
| (1) |
>
|
expand( GroupOrder( QuotientGroup( G, N ) ) );
|
| (2) |
|
The mere existence of the procedure `expand/GroupOrder` causes the procedure expand to call it when confronted with an unevaluated call to GroupOrder.
|
|
In the case of expand, the extension protocol specifies that the extension procedure, here `expand/GroupOrder`, be passed the arguments of the call to GroupOrder. In other cases (for instance, combine), the entire expression to be operated upon can be passed.
|
|
|
|
Modern Extension Mechanism
|
|
|
With the introduction of modules, it is now possible to program extensible procedures and packages in a namespace-aware way. That is, it is possible to define, for example, types that are local to a module. The built-in procedure type is one example of a user-extensible procedure that has been made namespace-aware. The TypeTools package allows you to define new types in such a way that local names can be type names. (For backward compatibility, type extensions may be defined using either the classical or modern extension mechanism.) An example of a library facility that makes use of the modern extension mechanism is the verify command.
|
|
The modern extension mechanism is normally associated with a package or other module that provides a standard API for adding, removing and querying extensions of the associated facility. Normally, at least three routines are provided: AddXXX, RemoveXXX, GetXXX. The XXX portion of the names depends upon the facility being extended. For example, the TypeTools package provides three exports AddType, RemoveType and GetType, that allow you to add a new type, remove an existing type, and query the value of an existing type. Additional procedures may be provided, as appropriate.
|
|
Because the modern extension mechanism stores each extension associated with the actual extension name (normally in a table), you can in fact define extensions that are local to a procedure or module; that is, the extension name need not be global, and distinct extensions whose name are instances of the same global name in a different namespace can coexist.
|
>
|
F := proc( s )
local integer; # name of a local type
TypeTools:-AddType( 'integer', proc( expr )
type( expr, 'INTEGER( string )' )
and StringTools:-IsDigit( op( 1, expr ) )
end proc );
# Test both the local and global `integer' types
type( s, 'integer' ), type( s, ':-integer' )
end proc:
|
| (3) |
>
|
F( 'INTEGER'( "34" ) );
|
| (4) |
|
Note that the local type integer does not interfere with the global type integer.
|
| (5) |
>
|
type( 'INTEGER'( "34" ), 'integer' );
|
| (6) |
|
(Indeed, in this case, the type persists, but there is no way to refer to the local type integer once the procedure F has returned. Moreover, each invocation of F creates a new local type integer. You can use TypeTools[RemoveType] to clean up local types that are no longer accessible.)
|
>
|
TypeTools:-GetTypes(); # one for each call to F()
|
| (7) |
>
|
convert( (7), 'set' ); # these are distinct locals
|
| (8) |
>
|
member( 'integer', (8) ); # neither is global
|
| (9) |
|
|
Using the Extension Mechanisms with Modules
|
|
|
Often, when you are developing a package or other module (or even, occasionally, within a procedure definition), you may want to extend a facility such as type or convert with knowledge that is associated particularly to the code you are developing. For example, you may want to have your package implement several types and conversions. While both extension mechanisms can be used while taking advantage of modules or local data in procedures, only the modern extension mechanism can be used with extensions whose names are local to a module or procedure. Thus, for example, a module can define types whose names are among its exports, because type uses the modern extension mechanism, but combine can only be extended using global extension names, since it uses the classical extension mechanism exclusively.
|
|
The advantage of defining a classical extension within a module (or procedure) is that the definition can make use of data local to the namespace in which it is defined.
|
|
The most common example in which the modern extension mechanism is used in modules is extending type with local types. To do this, you must use TypeTools[AddType] to define the local type. This can be done in the body of a module definition if the local type is to be used only in the same session in which the module is defined. More often, however, you will want to be able to save your module (say, a package), and have the type definition be effective once the module is read from the repository in which it is saved.
|
|
While you could manually check that your local types have been defined prior to use, and define them if they have not yet been defined, it is usually more convenient to use the load= module option. Any classical extensions defined in the procedure associated with the load= option must be declared global in that procedure (or the enclosing module). Note that it is the full name `extendme/foo`, rather than the extension name foo itself that must be declared global. The modern extension mechanism can also be used to define any local extensions. In this case, since there is no global name such as `extendme/foo` involved in the extension mechanism, only the extension name foo itself must be declared a local or exported local.
|
|
|
Example
|
|
|
The following example package illustrates three distinct ways to use the extension mechanisms to associate extensions with the package facilities. (The package simply defines addition and multiplication for a peculiar representation of integers, along with several extensions to illustrate the techniques.)
|
|
A new local type integer is defined and exported by the package. The local procedure setup is used to dynamically add this type whenever the package is read from a repository. The modern extension mechanism, that is supported by type, is used in this case.
|
|
A print extension is defined in the module. The name `print/INTEGER` is declared global in the module definition, because print uses only the classical extension mechanism. Both the package and the global name `print/INTEGER` must be saved separately in a repository. (Saving the package does not automatically save `print/INTEGER`.)
|
|
Finally, a custom conversion associated with this package is defined. While convert uses the classical extension mechanism, the setup procedure is used to add the conversion dynamically when the package is read.
|
>
|
FunnyIntegers := module()
description "strange integer operations";
option package, load = setup, unload = cleanup;
local setup, cleanup;
export a, m, integer;
global `print/INTEGER`;
# Define this global `print' extension.
`print/INTEGER` := proc( str )
if StringTools:-IsDigit( str ) then
parse( str )
else
error "invalid funny integer"
end if
end proc;
# Use this procedure to extend `type' and `convert' dynamically
# when this package is read from a repository.
setup := proc()
global `convert/INTEGER`;
TypeTools:-AddType( 'integer', # define local integer type
s -> type( s, 'INTEGER( string )' )
and StringTools:-IsDigit( op( 1, s ) ) );
`convert/INTEGER` := proc( n )
if type( n, ':-integer' ) then
'INTEGER'( convert( n, 'string' ) )
elif type( n, 'integer' ) then
n
else
error "unable to convert"
end if
end proc;
NULL
end proc;
setup(); # make this available now
# Remove these dynamic definitions when the package is garbage collected.
cleanup := proc()
global `convert/INTEGER`;
TypeTools:-RemoveType( 'integer' );
`convert/INTEGER` := evaln( `convert/INTEGER` );
NULL
end proc;
# Define some exports
a := proc( s, t )
local s1, t1;
if type( [ s, t ], '[ integer, integer ]' ) then
s1 := parse( op( 1, s ) );
t1 := parse( op( 1, t ) );
convert( s1 + t1, 'INTEGER' )
else
error "invalid input"
end if
end proc;
m := proc( s, t )
local s1, t1;
if type( [ s, t ], '[ integer, integer ]' ) then
s1 := parse( op( 1, s ) );
t1 := parse( op( 1, t ) );
convert( s1 * t1, 'INTEGER' )
else
error "invalid input"
end if
end proc;
end module:
|
| (10) |
>
|
type( 2, FunnyIntegers:-integer );
|
| (11) |
>
|
type( 'INTEGER'( "3" ), 'integer' );
|
| (12) |
>
|
type( 'INTEGER'( "3" ), 'FunnyIntegers:-integer' );
|
| (13) |
>
|
'INTEGER'( "42" ); # printing works
|
| (14) |
| (15) |
>
|
type( 2, 'integer' ); # local type is now bound
|
| (16) |
>
|
type( 'INTEGER'( "3" ), 'integer' );
|
| (17) |
>
|
type( 'INTEGER'( "3" ), ':-integer' ); # global type is still accessible by using the :- prefix
|
| (18) |
>
|
m( 5, a( 2, 3 ) ); # error
|
>
|
m( 'INTEGER'( "5" ), a( 'INTEGER'( "2" ), 'INTEGER'( "3" ) ) );
|
| (19) |
>
|
convert( 3, 'INTEGER' );
|
| (20) |
>
|
lprint( convert( 3, 'INTEGER' ) );
|
|
|
How to Implement an Extensible Facility
|
|
|
Your own Maple code can be made user-extensible by writing a module to manage the extensions. This is illustrated for the case of a single global command DoFoo, which does a simple transformation on expressions involving functions. The handling of unevaluated function calls that appear in input expressions is extensible by users by providing a function name and a transformation procedure to apply to all unevaluated function calls with the given function name.
|
|
The extension names and associated implementations are stored in a table local to the module Foo. The module is not a package, in this case, so the exports can be accessed only by their fully qualified names (for example, Foo:-AddFoo). The module Foo itself exports three routines AddFoo, GetFoo and RemoveFoo. The AddFoo routine installs extensions in the local table fooTab managed by this module. There is also a RemoveFoo export for deleting extensions previously added. Finally, an existing extension can be queried by using the export GetFoo.
|
>
|
Foo := module()
export AddFoo, GetFoo, RemoveFoo;
local fooTab;
global DoFoo;
fooTab := table():
AddFoo := proc( theName::symbol, implementation )
local old_imp;
old_imp := NULL;
if assigned( fooTab[ theName ] ) then
WARNING( "replacing existing extension `%1'", theName );
old_imp := eval( fooTab[ theName ] )
end if;
fooTab[ theName ] := eval( implementation, 1 );
eval( old_imp, 1 )
end proc;
RemoveFoo := proc( theName::symbol )
if assigned( fooTab[ theName ] ) then
fooTab[ theName ] := evaln( fooTab[ theName ] )
end if;
NULL
end proc;
GetFoo := proc( theName::symbol )
if assigned( fooTab[ theName ] ) then
eval( fooTab[ theName ], 1 )
else
NULL
end if
end proc;
DoFoo := proc( expr, foo )
if assigned( fooTab[ foo ] ) then
subsindets( expr, 'specfunc'( 'anything', foo ), fooTab[ foo ] )
else
error "cannot do %1", foo
end if
end proc;
end module:
|
|
In the following examples, DoFoo is extended with three extensions. Note, in particular, that two extensions named G, one global and one local to the module M are defined, and are distinct.
|
>
|
Foo:-AddFoo( 'F', proc( fn )
if type( fn, 'F( anything, anything )' ) then
'F'( op( 2, fn ), op( 1, fn ) )
else
fn
end if
end proc ):
Foo:-AddFoo( 'G', proc( fn )
if type( fn, 'G(algebraic)' ) then
'G'( - op( 1, fn ) )
else
fn
end if
end proc ):
M := module()
export G;
local setup;
load = setup;
setup := proc()
Foo:-AddFoo( 'G', proc( fn )
if type( fn, 'G(algebraic)' ) then
'G'( 2 * op( 1, fn ) )
else
fn
end if
end proc );
NULL
end proc;
setup();
end module:
DoFoo( F( x, y ) + G( s ) + M:-G( s ) + H( s ), F );
|
| (21) |
>
|
DoFoo( F( x, y ) + G( s ) + M:-G( s ) + H( s ), G );
|
| (22) |
>
|
DoFoo( F( x, y ) + G( s ) + M:-G( s ) + H( s ), M:-G );
|
| (23) |
>
|
DoFoo( F( x, y ) + G( s ) + M:-G( s ) + H( s ), H ); # no extension for H
|
|
|