Overview of Object Methods
Methods
Examples
Methods are procedures defined when declaring a Maple object. There are a few notes for defining and using object methods.
Method Names Should Be Declared static
In Maple, method names should be declared with type static. In most cases, all objects of the same class use the same procedures for their methods. Thus if the method names are not declared static, each object will have a separate copy of the procedure. For more information on using static with objects, see Creating a New Object.
A static method should not use lexical scoping to refer to non-static local or exported variables within the object. Lexically scoped references need to be updated when an object is copied (new instances created), but since a static method is shared between instances, this is not possible.
Instead, static methods should refer to the local or exported variables of one of the object parameters passed into the method (see the next section), using the object:-variable notation.
Lexical scoping may be used to refer to static variables. As these are shared between all instances of an object, references to them are the same for all instances.
As of Maple 2021, if the method has a formal parameter named _self, references to its object's local or exported variables may be written without prefixing them. That is, _self:-variable may be written as just variable. Maple will add the self:- prefix internally when the method is simplified.
Objects Must Be Passed into Methods
If you declare a method that needs to access the contents of an object, that object must be passed into the method as an argument. This is in contrast to some object oriented languages which allow access to the contents of an object associated with the current invocation, either through direct access to the object's members or through a self variable. The method will have access to the local variables of any object of the same class that the method was declared in.
As of Maple 2021, a message-passing form of method call can be used, which will automatically pass the object as an argument if the method has a formal parameter named _self.
In the following example, the set and toString methods expect an instance of the StringBuffer class as their first argument.
module StringBuffer()
option object;
local buffer := Array( 1..0, datatype=integer[1] );
export set::static := proc( _self::StringBuffer, s::string, $ )
_self:-buffer := Array( convert( s, ':-bytes' ), datatype=integer[1] );
NULL;
end;
export toString::static := proc( _self::StringBuffer, $ )
convert( convert(_self:-buffer, list), ':-bytes' );
export append::static := proc( _self::StringBuffer, s::string, $ )
local l, n;
l := convert( s, ':-bytes' );
n := :-numelems( _self:-buffer ) + 1;
_self:-buffer( n .. n + :-numelems(l) - 1 ) := Array(l);
end:
Calling Methods
Calling an Exported Method
To call a method that is exported by an object, use the standard function calling syntax with the object as an argument. When evaluating the function call, Maple will search the arguments, left to right, for an object that exports a method with the same name as the specified function. The first object found with a matching method will have its method called with the given arguments.
> sb := Object(StringBuffer);
sb≔Object<<StringBuffer,140617324349632>>
> set( sb, "Hello" );
> append( sb, " " );
> append( sb, "World" );
> toString( sb );
Hello World
Using the function mechanism
The standard way to invoke a method exported by an object will not work if the name of the method has a value as a global variable (unless that value has last name evaluation, for example, if it is a procedure or a module that isn't an object). Here is an example:
> toString := 5;
toString≔5
5
To overcome this, you can replace a call of the form method_name( arguments ) with function:-method_name( arguments ) (using the literal name function) or object:-method_name( arguments ) (using the object instance on which you would like to call the method). This works regardless of the value of the global variable.
> function:-toString( sb );
> sb:-toString( sb );
You can find more information about the function mechanism on the object/function_mechanism help page.
Using the Message Passing Mechanism
Another way to invoke a method, similar to that used in other object-oriented languages, was introduced in Maple 2021. In this form, the object name is qualified by the method name, object_name:-method_name( argument ) just as it can be in the function mechanism described above. However, the object can be omitted from the argument sequence.
For this form to work, the method must have been implemented using the name _self for the parameter that is to receive the object. That parameter must be a required positional parameter, and must have either no type specification (_self), or a type specification that is the class name (e.g., _self :: StringBuffer).
> sb:-set( "Hello" );
> sb:-append( " " );
> sb:-append( "World" );
> sb:-toString();
Calling a local Method
Locals can only be accessed by methods defined within the object's declaration, so normal scoping rules are usually sufficient to resolve the function call to the correct procedure.
Objects in Indexed Function Calls
When making an indexed function call (of the form func[index](args)) Maple will also check the indices (index) for a matching object as well as the arguments. If a matching object is found in the indices, that object will be used before one found in the arguments.
Using ModuleCopy and ModuleApply to Create an Object Factory
The special ModuleCopy and ModuleApply methods can be combined to implement an object factory routine. The ModuleCopy method is used to define what happens when the Object routine is called to create a new object. The ModuleApply method defines what happens when an object name is applied (that is, used as a function call).
By adding the following methods to the declaration of StringBuffer, the ModuleApply method becomes a factory routine.
export ModuleCopy::static := proc( _self::StringBuffer, proto::StringBuffer,
s::{string,StringBuffer,':-list'( ':-integer[1]' )}, $ )
if ( _npassed = 3 ) then
if ( s::':-string' ) then
set( _self, s );
elif ( s::':-list'( ':-integer[1]' ) ) then
_self:-buffer := Array( s, datatype=integer[1] );
else
_self:-buffer := Array( s:-buffer );
_self:-buffer := Array( proto:-buffer );
export ModuleApply::static := proc( )
Object( StringBuffer, _passed );
Having defined a ModuleCopy and ModuleApply, we can now call StringBuffer like a function to create new instances.
> sb2 := StringBuffer();
sb2≔Object<<StringBuffer,140617193142400>>
> set( sb2, "StringBuffer" );
> toString( sb2 );
> sb3 := StringBuffer( "Another Buffer" );
sb3≔Object<<StringBuffer,140617193118272>>
> toString( sb3 );
> sb4 := StringBuffer( sb3 );
sb4≔Object<<StringBuffer,140617192607072>>
> toString( sb4 );
There are other special method names that can be defined. For a complete list of these methods, see the Overview of Objects in Maple page.
Operator Methods
Objects in Maple can implement operator methods. These methods will be invoked when objects are used with the corresponding operators in Maple. One can implement the = operator for StringBuffer as follows:
export `=`::static := proc( s1, s2, $ )
if ( _npassed = 1 ) then
return false;
if ( not s1::{string,StringBuffer} or
not s2::{string,StringBuffer} ) then
if ( s1::string ) then
evalb(s1 = toString( s2 ));
if ( s2::string ) then
evalb(toString( s1 ) = s2);
elif ( s1 = s2 ) then
true;
EqualEntries( s1:-buffer, s2:-buffer );
> sb1 := StringBuffer( "Hello" );
sb1≔Object<<StringBuffer,140617192579200>>
> sb2 := StringBuffer( "Hello" );
sb2≔Object<<StringBuffer,140617192569536>>
> sb3 := StringBuffer( "Goodbye" );
sb3≔Object<<StringBuffer,140617192100928>>
> evalb( sb1 = sb2 );
true
> evalb( sb1 = sb3 );
false
> evalb( sb1 = 1 );
Note Checking for the 1 argument case handles the possibility that an object is compared to NULL.
> evalb( sb1 = NULL );
There are many operators that can be overloaded. For a complete list, and more information about overloading operators, see the Objects Overloading Operators page.
Overriding builtin Routines
Objects can override certain Maple routines of type builtin by defining a method with the same name. For example, StringBuffer can implement a numelems member as follows.
export numelemsstatic := proc( _selfStringBuffer, s:string, $ )
:-numelems( self:-buffer );
When calling the top level numelems routine, we need to use the :- notation to differentiate from StringBuffer's numelems method. Without this the StringBuffer numelems method would have an infinite recursion.
With the numelems method defined, we can pass StringBuffer objects into numelems, just as we could other Maple structures.
> numelems( [ 1, 2, 3 ] );
3
> numelems( "Hello" );
> numelems( sb );
11
> sb:-numelems();
Not all builtin routines can be overridden. For a complete list of routines that can be overridden and more discussion of overriding builtin routines, see Objects Overloading builtin Routines.
Here is the code from the sections above as a single declaration.
module StringBuffer() option object; local buffer := Array( 1..0, datatype=integer[1] ); export ModuleCopy::static := proc( _self::StringBuffer, proto::StringBuffer, s::{string,StringBuffer,':-list'( ':-integer[1]' )}, $ ) if ( _npassed = 3 ) then if ( s::':-string' ) then set( _self, s ); elif ( s::':-list'( ':-integer[1]' ) ) then _self:-buffer := Array( s, datatype=integer[1] ); NULL; else _self:-buffer := Array( s:-buffer ); NULL; end; else _self:-buffer := Array( proto:-buffer ); NULL; end; end; export ModuleApply::static := proc( ) Object( StringBuffer, _passed ); end; export numelems::static := proc( _self::StringBuffer, s::string, $ ) :-numelems( _self:-buffer ); end; export `=`::static := proc( s1, s2, $ ) if ( _npassed = 1 ) then return false; end; if ( not s1::{string,StringBuffer} or not s2::{string,StringBuffer} ) then return false; end; if ( s1::string ) then evalb(s1 = toString( s2 )); else if ( s2::string ) then evalb(toString( s1 ) = s2); elif ( addressof(s1) = addressof(s2) ) then true; else EqualEntries( s1:-buffer, s2:-buffer ); end; end; end; export set::static := proc( _self::StringBuffer, s::string, $ ) _self:-buffer := Array( convert( s, ':-bytes' ), datatype=integer[1] ); NULL; end; export toString::static := proc( _self::StringBuffer, $ ) convert( convert(_self:-buffer,list), ':-bytes' ); end; export append::static := proc( _self::StringBuffer, s::string, $ ) local l, n; l := convert( s, ':-bytes' ); n := :-numelems( _self:-buffer )+1; _self:-buffer( n..n+:-numelems(l)-1 ) := Array(l); NULL; end; end;
See Also
module
ModuleApply
ModuleCopy
Object
object
object/builtin
object/create
object/function_mechanism
object/operators
procedure
Download Help Document