The 'Function' Mechanism of Invoking Object Methods
Invoking Object Methods
Example Classes
Global Variable Values
Applying an Appropriate Function to Objects of Various Types, and to Non-objects
Procedure Name Indices
Compatibility
If o is an object with a method called f, you can generally invoke it as follows:
f(o);
f(o, p, q);
f(m, n, o, p);
The last case will not invoke the f method of the object o if m or n is an object that has a method called f: then it invokes the f method of that object. This is explained in greater detail on the help page object/methods.
This help page discusses some specialized use cases where this way of method invocation has some disadvantages, and an alternative that can be used in those cases.
We will use the following concrete example throughout this help page, of a Fruit parent class with two subclasses.
module Fruit() option object(); local color::string := "", ModulePrint::static, ModuleCopy::static, ModuleApply::static; export SetColor::static, GetColor::static, convert::static; # Call the constructor. ModuleApply := proc() return Object(eval(procname), _passed); end proc; ModuleCopy := proc(this, other, color::string, $) this:-color := color; end proc; ModulePrint := proc(this, $) :-convert(this, 'name'); end proc; # Return previous value of color. SetColor := proc(this, color::string, $) local old_color := this:-color; this:-color := color; return old_color; end proc; GetColor := proc(this, $) if type(procname, 'indexed') and op(procname) = 'capitalize' then return StringTools:-Capitalize(this:-color); else return this:-color; end if; end proc; convert := proc(this, target, $) if target in ['string', 'name', 'symbol'] then return :-convert(nprintf("< %s piece of generic fruit >", this:-color), target); else error "cannot convert a Fruit object to %1", target; end if; end proc; end module;
< piece of generic fruit >
module Apple() option object(Fruit); # Whether this apple still has its core. An apple starts out having a core, but calling # the Core method removes it. local core::truefalse := true; export Core::static, HasCore?::static; # Override Fruit's copy constructor ModuleCopy := proc(this, other, color::string, $) this:-color := color; end proc; # Override Fruit's method convert := proc(this, target, $) if target in ['string', 'name', 'symbol'] then return :-convert( nprintf("< %s%s apple >", `if`(this:-core, "", "cored "), this:-color), target); else error "cannot convert an Apple object to %1", target; end if; end proc; Core := proc(this, $) if not this:-core then error "you attempted to core an apple that has already been cored"; end if; this:-core := false; return NULL; end proc; HasCore? := proc(this, $) return this:-core; end proc; end module;
< apple >
module Orange() option object(Fruit); # Whether this orange still has its peel. An orange starts out having its peel, but calling the # Peel method removes it. local peel::truefalse := true; export Peel::static, WasPeeled?::static; # Override Fruit's copy constructor. You cannot specify the color of an orange: it's always # "orange". ModuleCopy := proc(this, other, $) end proc; # Override Fruit's method convert := proc(this, target, $) if target in ['string', 'name', 'symbol'] then return :-convert( nprintf("< %s orange >", `if`(this:-peel, "unpeeled", "peeled")), target); else error "cannot convert an Apple object to %1", target; end if; end proc; # Override Fruit's method SetColor := proc(this, color::string, $) if color <> "orange" then error "an orange can't be any color other than orange"; end if; return "orange"; end proc; # Override Fruit's method GetColor := proc(this, $) return "orange"; end proc; Peel := proc(this, $) if not this:-peel then error "you attempted to peel an orange that has already been peeled"; end if; this:-peel := false; return NULL; end proc; WasPeeled? := proc(this, $) return not this:-peel; end proc; end module;
< unpeeled orange >
You can use these classes as follows.
fruit := Fruit("purple");
fruit≔< purple piece of generic fruit >
SetColor(fruit, "red");
purple
fruit;
< red piece of generic fruit >
convert(fruit, 'string');
GetColor(fruit);
red
apple1 := Apple("red");
apple1≔< red apple >
apple2 := Apple("green");
apple2≔< green apple >
Core(apple2);
apple2;
< cored green apple >
Error, (in Apple:-Core) you attempted to core an apple that has already been cored
HasCore?(apple1);
true
HasCore?(apple2);
false
SetColor(apple2, "yellow");
green
< cored yellow apple >
orange := Orange();
orange≔< unpeeled orange >
SetColor(orange, "blue");
Error, (in Orange:-SetColor) an orange can't be any color other than orange
Peel(orange);
orange;
< peeled orange >
If the method name, say f, that you are trying to invoke, is in use as a global variable, then the standard way of invoking it may not work. In particular, if a value is assigned to this global variable, and that value does not have last name evaluation, then the variable will evaluate to its value before Maple has a chance to detect that it is intended to be used as a method invocation.
For example, below we assign to the variable SetColor. We then have trouble invoking the SetColor method.
SetColor := 5;
SetColor≔5
SetColor(apple2, "light red");
5
What happened here is that SetColor evaluated to the number 5. Next Maple evaluates 5(apple2, "light red"). Maple uses the fact that numeric constants also represent constant functions, as explained on the function help page. So 5(anything) evaluates to the number 5.
Presumably, we wanted to call the method of apple2 instead. There are two ways to ensure this: explicitly name apple2 as the namespace where SetColor is to be understood, or use the function mechanism. We will see that both of these ways work in most situations. For example:
apple2:-SetColor(apple2, "light red");
yellow
< cored light red apple >
function:-SetColor(apple2, "greenish yellow");
light red
< cored greenish yellow apple >
You can indicate a namespace using the colon-dash operator, also known as the member selection operator. For example, apple2:-SetColor above refers to the SetColor method of the apple2 object, without regard for the global variable SetColor and its value, 5.
There is a special namespace, accessed by using the literal name function as the left-hand side of the colon-dash operator. To explain how this works, consider the case where a name from this namespace occurs as the function name in a function call (that is, as the zeroth operand of a function call). That is, we have something like function:-SetColor(apple2, "greenish yellow"). In that example, the function name is function:-SetColor from the function name space. To evaluate such a function call, Maple first checks to see if any of the arguments to the function call are objects that export this name, and the value of that name is a procedure (or an appliable module). This is the case in the example: apple2 is an object exporting the name SetColor, and the value of apple2:-SetColor is a procedure. If so, Maple executes the procedure with the given arguments. We will see what happens otherwise in the next section.
In the example above, the object we wanted to invoke a method on was stored in a variable. If this is not the case, you can still use both methods. For example, the object may be the result of another procedure invocation. Specifying the object you are invoking the method on explicitly may be inefficient in this case: it would require invoking the procedure twice. Using the function mechanism does not have this disadvantage. An example of this follows.
# A slow procedure to generate a new red apple. red_apple := proc($) WARNING("generating a red apple..."); Threads:-Sleep(2); return Apple("red"); end proc;
red_apple:=proc $WARNING⁡generating a red apple...;Threads:-Sleep⁡2;returnApple⁡redend proc
To generate a new red apple and find out what color it is, we can use the two methods explained above.
red_apple():-GetColor(red_apple());
function:-GetColor(red_apple());
The function mechanism is more efficient in this case.
A similar reason may apply if the object can be efficiently computed, but it is just a very long expression to type. In this case, it may look more aesthetically pleasing to use the function mechanism. For example, if the object exists in a deeply nested record, as in the following example.
inventory := Record('utensils' = Record(), 'food' = Record('vegetables' = Record(), 'fruits' = Record('apple' = Apple("green"), 'orange' = Orange())));
inventory≔Record⁡utensils=Record⁡...,food=Record⁡...
To find out the color of the apple, we can use the two methods explained above.
inventory:-food:-fruits:-apple:-GetColor(inventory:-food:-fruits:-apple);
function:-GetColor(inventory:-food:-fruits:-apple);
If you have implemented a method with a particular name for different types of objects (such as GetColor or convert for all objects discussed here), and you want to invoke the appropriate method on all elements in a collection of such objects, it can be particularly convenient to use the function mechanism.
fruits := [Apple("red"), Orange(), Apple("green")];
fruits≔< red apple >,< unpeeled orange >,< green apple >
map(function:-GetColor, fruits);
red,orange,green
Now consider the following case. You want to apply a command f to a list of expressions. Some of these expressions are objects implementing a method named f. Others are not; for those expressions you would like to apply the procedure that is the value of the global variable f. In this case, you can also use the function mechanism; this is where we pick up the explanation of the function mechanism from the description in the previous section. To evaluate a function call where the function name (the zeroth operand) is of the form function:-f, and none of the arguments are objects implementing a method f, Maple examines the value of the global variable f. If that value is a procedure or an appliable module, then Maple calls that procedure or appliable module with the given arguments.
In this case, you could also just use the global name, since its value is a procedure or appliable module, and thus it has last name evaluation. For symmetry with the previous case, we made it possible to use the function mechanism in this case, too.
things := [Apple("red"), sin(x), Orange()];
things≔< red apple >,sin⁡x,< unpeeled orange >
map(function:-convert, things, 'string');
< red apple >,sin(x),< unpeeled orange >
If you use the function mechanism to invoke a method or a standalone procedure, and you use indices on the method or procedure name, these indices will appear to be attached to the procname symbol when you evaluate it inside the procedure.
function:-GetColor['capitalize'](apple2);
Greenish Yellow
The function mechanism of invoking object methods was introduced in Maple 2016.
Download Help Document