Next: Collections, Previous: File input and output, Up: Top [Index]
self
and super
The self
and super
keywords refer to receivers and
superclass methods at run time. They allow you to write methods
without knowing beforehand what objects called them.
self
as a ReceiverWithin a method, self
refers to the receiver of the method that
self
appears in. Within the method, you can use self
interchangeably with other receivers.
When used as a receiver, Ctalk’s self
works much like
the self
keyword in Smalltalk and the this
keyword in C++.
In this example, self
in the method add2
refers to
the method’s receiver, myInt,
which is declared in main.
Integer instanceMethod add2 (void) { methodReturnInteger(self + 2); } int main () { Integer new myInt; Integer new myTotal; myInt = 2; myTotal = myInt add2; printf ("%d\n", myTotal); }
You need to be careful of using self
within a constructor (a
new
method), because the method’s receiver is the class object
of the instance you are creating, and not the new object itself, and
that is probably not what you want. Ctalk prints a warning message in
this case if you enable verbose warnings with the ‘-v’ command
line option.
super
KeywordWhen used before a message, super
tells Ctalk that the
message refers to a method in the superclass of the receiver.
One use of super
is in constructors of subclass objects. The
keyword allows constructor methods, which are almost always called
new,
to refer to the constructor of a superclass, which allows
subclass objects to inherit the instance variables of its
superclasses.
The example in the first chapter, of how FileStream
and its
subclasses inherit instance variables, describes how constructors can
call class constructors. See Class Hierarchy.
To show how super
is used, here is the new
method from
class WriteFileStream
.
WriteFileStream instanceMethod new (char *name) { WriteFileStream super new name; __ctalkInstanceVarsFromClassObject (name); WriteFileStream classInit; return name; }
super
Used as a ReceiverWhen used within an argument block, super
can refer to
the receiver of the method that contains the argument block.
This overloading of super
conveniently allows methods
to refer to both the collection that maps the argument block
(by referring to super
within the block), and to each
element of the collection (by referring to self
within the block).
To help make this clear, look at the following example.
List instanceMethod mapPrint (void) { String new sPrefix; String new s; Integer new n; sPrefix = "This element is "; n = 0; /* * Here, "self," refers to the receiver of the mapPrint method, * the List l that was declared in main (). */ self map { /* * Within the code block, though, "self," refers to each element * of List l in succession. */ s = sPrefix + self; printf ("%s\n", s); /* * Here, "super," refers to the List l, because it's * the receiver of the mapPrint method which contains * the argument block. */ super push "l3"; /* break after printing "...l1," "... l2," and four "... l3" strings. */ n = n + 1; if (n >= 6) break; } return NULL; } int main () { List new l; l push "l1"; l push "l2"; l mapPrint; exit(0); }
If you use super
in an argument block within a function, which
doesn’t have a receiver object, then Ctalk uses the object that is the
receiver of map
or whatever iterative method has the code
block as its argument.
In this case, Ctalk issues a warning so that you know which receiver the argument block actually refers to.
self
Generally, the receiver object that self
refers to belongs to
the same class as the method where it appears. But there are many
occasions when a method cannot assume that it knows in advance the
class of self
, or the result of an expression that contains
self
. This section describes a few of these situations and how
to deal with them.
In many cases, programs can use the method is
(defined in
Object
class) to determine the class of an object. The
argument to is
must be a declared class, and the method
returns True or False, depending on whether the receiver is
an instance of the class given as the argument.
To determine if an object is a class object, use isClassObject
(also defined in Object
class), to determine if an object is a
class object.
These examples illustrate the different expressions.
if (self is String) { /* OK - String is a defined class. */ ... } if (self is Class) { /* Not OK - 'Class' is not a defined class. */ ... } if (self isClassObject) { /* OK - Just check whether the object is ... its own class object. */ }
But there are also many cases where the class of a receiver is not
be so clear-cut. A method might be declared in a superclass of the
receiver, or the method might need to work objects that are members of
a collection, as in this example. A method might belong to the class
of a collection like List
or AssociativeArray
, but the
collection’s elements may be another class entirely.
In collections, the receiver object is actually a group of Key
class objects, which is the “glue” class that provides the
organization of the collection’s members. Each Key
object
contains a reference to an object that is part of the collection’s
actual contents.
The method below is from the ctcheckquery.c
example program,
and the receiver contains the parameters and values of a CGI query.
This method can be relatively simple because it can assume beforehand
that the values it is going to print are instances of String
class. The actual receiver is an instance of Key
class, so
self
in this method understands both the name
and
getValue
methods, which are defined in Symbol
class,
which is the superclass of Key
class.
AssociativeArray instanceMethod printQueryParamPair (void) { printf ("<tt>%s=", self name); printf ("\"%s\"</tt><br>\n", self getValue); return NULL; }
If a method needs to perform more complex operations on collection
members, it can often cast the collection member to an object of a
known class with either become
or just plain =
, depending
on the class. This hypothetical example shows one example of this.
TreeNode new head; TreeNode new siblingObject; ... /* Do some stuff. */ head siblings map { siblingObject become self; /* Ctalk waits until run-time to evaluate a "self" argument. */ siblingObject children map { ... /* Do some more stuff. */ } }
You can also use a Symbol
object to refer to self.
Symbol class is designed to handle references of different classes,
so this example also works.
TreeNode new head; TreeNode new tChild; Symbol new tSib; .... /* Do some stuff. */ head siblings map { *tSib = self; content = "Child Node"; tChild = TreeNode basicNew content; tChild setContent content; tSib getValue addChild tChild; }
Still easier, but less flexible, is the eval
operator, which tells
the compiler to wait until run time before actually evaluating the
complete expression.
X11Pane instanceMethod deleteAndClose (void) { self children map { eval self deleteAndClose; } ... }
And finally, you can use a cast operator to tell the compiler the
class of self
. This feature is still experimental, but it is
often more convenient than using eval
or creating an
intermediate object.
TreeNode new head; head siblings map { printf ("%s\n", self content); (TreeNode *)self children map { /* Here, "(TreeNode *)" tells the compiler that we expect self to be a TreeNode object. */ printf (" %s\n", self content); } }
If you must use a complete, original receiver object from a collection class, then the method cannot alias the original receiver to a local object.
In this case, the program can use another method to dispatch the
receiver’s message to the correct at run time. For this, the dispatch
method can use the eval
keyword.
This situation is frequently the case with complex objects. Here is
an example from ANSIYesNoBoxPane
class that selects the highlight
of an already selected input pane.
The receiver of the focusButton
method’s map
message is the widget’s list of child windows (a List
), and
this method checks the list element’s class and dispatches the
program to the highlightButton
method of class
ANSIButtonPane
.
ANSIButtonPane instanceMethod highlightButton (void) { if (self hasFocus) { self focusHighlightOnOff; } else { self resetGraphics; } return NULL; } List instanceMethod highlightButton (void) { Exception new e; if (self value is ANSIButtonPane) { eval self highlightButton; } else { e raiseCriticalException INVALID_RECEIVER_X, "Receiver of \"highlightButton\" is not an ANSIButtonPane object"; } return NULL; } ANSIYesNoBoxPane instanceMethod focusButton (void) { self children map highlightButton; return NULL; }
Note that when checking the class, the program uses
if (self value is ANSIButtonPane) { ...
This is because the receiver, a button widget, is an instance variable
of an ANSIYesNoBoxPane
object, so its member class is also
ANSIYesNoBoxPane
, but its value is
ANSIButtonPane
.
Exception
handling, for example the use of the
Exception
object in the List
implementation of
highlightButton
, is discussed further in the section,
Error Handling and Debugging. See Debugging.
You can also write most of a method in C, if necessary.
Here is the asInteger
method from class Magnitude.
Generally, only the Object
class is completely evaluated before
this method, so asInteger
needs to perform many of its
operations in C. However, it can use the method is
from
Object
class to determine the class of the receiver.
Magnitude instanceMethod asInteger (void) { OBJECT *self_value; int i; long long int l; returnObjectClass Integer; self_value = self value; if (self is Integer) { return self; } else { if (self is LongInteger) { sscanf (self_value -> __o_value, "%lld", &l); if (l > MAX_UINT) _warning ("Overflow in type conversion.\n"); i = (int)l; } /* * Character to int conversion, plus escape sequences. */ if (self is Character) { if (*self_value -> __o_value == '\'') { if (self_value -> __o_value[1] == '\\') { switch (self_value -> __o_value[2]) { case 'a': i = 1; break; case 'b': i = 2; break; case 'f': i = 6; break; case 'n': i = 10; break; case 'r': i = 13; break; case 't': i = 9; break; case 'v': i = 11; break; case '0': i = 0; break; default: i = (int) self_value -> __o_value[2]; break; } } else { i = (int) self_value -> __o_value[1]; } } else { i = (int) *self_value -> __o_value; } } /* if (self is Character) { */ if (self is Float) { /* * In case Exception class isn't loaded yet, simply * print a warning. */ _warning ("asInteger (class Magnitude): Receiver truncated to Integer.\n"); i = (int)atof (self); } } methodReturnInteger (i); }
Often a program can determine the class of a method’s return object
from the class of the receiver. Sometimes, however, the
returnObjectClass
statement is necessary. Consider the
following example from X11TerminalStream
class.
X11TerminalStream instanceMethod parentPane (void) { OBJECT *receiver_alias, *parent_alias; returnObjectClass X11Pane; receiver_alias = self; if (receiver_alias -> __o_p_obj) { parent_alias = receiver_alias -> __o_p_obj; } else { parent_alias = NULL; } return parent_alias; } ... self parentPane origin x = event_data1;
If Ctalk couldn’t determine that parentPane
returns a
X11Pane
object, it would not be able to determine that the
class of origin
(an instance variable of X11Pane
) is a
Point
, and that x
is an instance variable of
origin
.
Defining the object return class also helps in checking the class of arguments in complex statements. This helps Ctalk determine what is and isn’t a method argument, as in this example.
Integer new intA; Integer new intB; if (intA + intB == 4) do something;
Here, Ctalk can determine that ==
isn’t part of the argument to
+
, because it returns a Boolean
, while +
expects an
Integer
or an expression that returns an Integer
as its
argument.
The method’s return class does not actually need to be
defined when Ctalk encounters the returnObjectClass
statement. Ctalk checks later, after evaluating all of the
input, that the class is defined and prints a warning if it
still can’t find the class.
While we’re on the subject, we should mention that methods do not always know the class of their arguments, either. If the class of a method argument can vary, that makes it even more difficult to handle complex objects that contain numerous instance variables.
One way to deal with arguments of different classes is to duplicate
the argument into the class the method needs, as in this example,
the =
method from Integer
class.
Integer instanceMethod = set_value (int __i) { Integer new localInt; localInt become __i asInteger; __ctalkAddInstanceVariable (self, "value", localInt value); return self; }
Remember, a method can specify the class or type of an argument, but that doesn’t mean, when the program runs, that the argument actually is the class that the method expects.
Another issue is trying to print objects. If you were to write a method like the following.
MyClass instanceMethod myMethod (Object myArg) { printf ("%s\n", myArg myInstanceVariable); return NULL; }
Then you would need to make sure that myInstanceVariable is a
String
. If possible, Ctalk tries to match the class of an
expression with its context; in the example above, if Ctalk cannot
determine the class of myInstanceVariable
from the context of
the method, it will try to translate it into a char *
argument
for printf
.
There are a few ways that a method can determine the class of an
expression. One is to use the Object
method is
, as in
the asInteger
method in the previous section.
Another solution is to “cast” the argument to a local object using,
for example, the Object
method become
, as in the =
(class Integer
) example above.
Yet another way to deal with an unknown argument class is to use a
printOn
method. The method is better than printf
at
determining an object’s class at run time.
MyClass instanceMethod myMethod (Object myArg) { WriteFileStream classInit; stdoutStream printOn "%s\n", myArg myInstanceVariable; return NULL; }
Many classes implement a printOn
method, and it is relatively
easy for a class that you write to define a printOn
method also.
The Ctalk libraries take care of much of the work of translating
objects of different classes and formatting them correctly.
The printOn
methods can also use receivers other than stream
classes, so a method could use printOn to format a String, as in this
example.
String instanceMethod myIntConv (int myArg) { self printOn "%s\n", myArg myIntVariable; return self; }
Next: Collections, Previous: File input and output, Up: Top [Index]