Next: , Previous: , Up: Top   [Index]


Self and super

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 Receiver

Within 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.

The super Keyword

When 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 Receiver

When 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.

Determining the Class of 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);
  }

}

Method Dispatchers

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);
}

Class of a Method Return Object

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.

Determining the Class of Method Arguments

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: , Previous: , Up: Top   [Index]