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


Methods

Writing Methods

Languages like C identify and call functions by matching a label in the source code against an identifier in an object module, and the compiler (or more properly, the linker or interpreter) inserts a call to the function in the program.

In Ctalk, like many other object oriented languages, objects respond to messages that refer to methods defined by the object’s class or the object’s superclasses.

Note: The following sections contain a lot of material, the importance of which may not be immediately clear. If necessary, you should skip ahead to the section, Assigning Values, to see how these concepts fit together. See Assigning Values.

Here is an example. If you wanted to create a new WriteFileStream object, you would add a statement like this to the program.

WriteFileStream new myOutputStream;

For the moment, we should also mention that most constructor methods should first contain a ‘super new’ message like this one. (We’ll discuss the process in more detail later on.)

WriteFileStream super new fileStreamName;

When the receiver, WriteFileStream, receives the message, new, the process of executing the methods that create a WriteFileStream object works in the following manner.

Class                  Message    Action 
-----                  -------    ------
|- Object <-------------- new        2. Return new, generic object.
|                           ^                  |
|- Stream                    \                 |
|                             \                |
|- FileStream                  \               |
|                               \              |
|- WriteFileStream <----- new     1.Execute    |
                                  "super new." |
                                               v
                                              3. Add the instance variables 
                                              that WriteFileStream objects 
                                              need and initialize the class 
                                              if necessary.

Here is a typical method. This is the instance method parseQueryString (class CGIApp.) The CGIApp class is in the Ctalk demos/cgi subdirectory and is used by the Common Gateway Interface programs in that directory.

CGIApp instanceMethod parseQueryString (void) {
  String new queryString;
  String new paramString;
  Array new queryParams;
  Array new paramArray;
  Integer new nParams;
  String new paramKey;
  String new paramValue;
  Integer new i;
  Integer new j;

  queryString = self serverEnvironment at "QUERY_STRING";

  if ((queryString length == 0) || (queryString isNull)) {
    return NULL;
  }

  nParams = queryString split '&', queryParams;

  for (i = 0; i < nParams; i = i + 1) {
    paramString = queryParams at i;
    j = paramString split '=', paramArray;
    if (j == 1) {
      self queryValues atPut (paramArray at 0), "1";
    } else {
      if (j == 2) {
	self queryValues atPut (paramArray at 0), (paramArray at 1);
      }
    }
  }  
  return NULL;
}

The parseQueryString method parses the parameters of a CGI query and places them in the application’s queryValues instance variable, an AssociativeArray. The queryValues instance variable then contains the key-value pairs of each of CGI application’s parameters.

If you call a CGI application with a URL like the following example

http://my-web-server/cgi-bin/mysearchprog?term=ctalk&case=ignore&showresults=10

Then parseQueryString sets the application’s queryValues AssociativeArray to the following.

                  Key          Value
queryValues  at   term         ctalk
queryValues  at   case         ignore
queryValues  at   showresults  10

The parseQueryString method uses the split method (class String) several times to split the query string at the ‘&’ delimiters, and then each parameter and its value at the ‘=’ character. If a parameter is defined but does not have a value associated with it, parseQueryString assigns it the value of ‘1’.

You should also note that the method checks for both a zero-length input value, and an uninitialized input value with the method isNull, which might occur, in this example, if the environment variable ‘QUERY_STRING’ is not defined.

Overview of the Method Application Programming Interface

You might recall from the earlier chapters that if a program uses an operator like ‘+’ or ‘-’ with a C variable, then Ctalk treats the operator as a C math operator. However, if you use a math operator with an object as the receiver, then Ctalk uses a method to perform the operation.

Ctalk’s base classes and methods are located in Ctalk’s class library. On UNIX systems, the class library normally resides in /usr/local/include/ctalk, although the location may vary depending on the operating system.

If you write your own methods, you might want to keep them in their own directory. In that case, the ‘-I’ command line option includes the directory given as its argument in the class search path. To include the current directory in the class search path, the command line option ‘-I .’ is a convenient shorthand.

Primitive Methods

One exception is the way Ctalk implements primitive methods. Ctalk implements these methods in C, and you can call them without worrying if a program defines a class.

These methods are available to all classes, so they effectively belong to the Object class. You can also subclass these methods, as is often the case with the constructor new. The previous chapters have described in general how to subclass methods, and this manual describes the process in detail later on.

Here are the primitive methods that Ctalk defines.

class classname

Defines a new class. The receiver is the name of the superclass, and the argument is the name of the class that you want to create.

classMethod alias selector (args)

Declares a new class method. The next section describes the syntax of method declarations. See Method Declarations.

classVariable varname varclass initial-value

Declare a class variable. The receiver is the class of the variable. You must provide a name for the class variable. The statement also allows you to define a class for the class variable and an initial value. See Class Variables.

instanceMethod alias selector (args)

Declares a new instance method. The next section describes the syntax of method declarations. See Method Declarations.

instanceVariable varname varclass initial-value

Define an instance variable. The receiver is the class of the variable. Unlike class variables, instanceVariable only defines a variable’s declaration. A constructor like new creates the actual instance variables when it creates an object. See Instance Variables.

new newobjectname

Create a new object. The receiver of new is the object’s class.

Note: You might need to use only primitive methods and library functions if you need to define a method in a basic class like Object or Symbol, where a method can’t always depend on a class being defined. During initialization, Ctalk programs require a number of basic classes: Object, Symbol, Integer, and ArgumentList, and their required classes Magnitude and Character. If, for example, you define a method in class Symbol to print objects, you cannot use WriteFileStream class, which needs Symbol and its methods already defined. So if you write a method that needs classes other than these basic classes, it is better to define a class specifically for your program.

Instance and Class Variable Messages

For completeness, we should mention that the other exception in the method API is the use of instance and class variable names as messages.

The value message is an example of a message that refers to an instance variable. Remember that a statement like the following example returns only the instance variable, and not the complete object.

return self value;

Instance variable messages are useful mainly when creating C references to objects. Methods, on the other hand, generally work with complete objects. An example is the at method in class Array.

myElement = myArray at 0;

The message at returns the 0th array element.

When you use objects with C, you must also check to determine that the object references are really the objects that the program expects.

The following sections describe the Ctalk interface to C, and the many of the examples in this manual describe Ctalk’s C API.

Method Declarations

As you probably noticed from the example above, you can declare methods in much the same manner as C functions.

However, method declarations have a different meaning, and the declarations allow for multiple methods with the same name in different classes.

The following example is the basic syntax of an instance method declaration.

class instanceMethod [alias] selector (args) {
... Method body
} 

The third term, alias, is optional. It is often used to alias a method to an operator like ‘+’, ‘-’, or another operator that you want to overload in class.

The syntax of a class method declaration is similar, but it uses the classMethod method.

class classMethod [alias] selector (args) {
... Method body
} 

How Methods Interface with C

Methods can be either labels or C operators. Although Ctalk’s syntax is similar to C, it allows more flexibility in the construction of statements.

As we noted earlier, you can change characters from upper case to lower case and vice versa by toggling the fifth bit of the character. Here is that example again.

Character new upperCase;
Character new lowerCase;
Integer new toggleBit;  

upperCase = 'Z';
lowerCase = 'z';
toggleBit = 32;

printf ("%c\n", upperCase ^ toggleBit);
printf ("%c\n", lowerCase ^ toggleBit);

The ^ operator in these statements is actually a method in class Character.

Ctalk also provides two methods in Character class, toUpper and toLower, that provide the same function, and the methods also check that the receiver is a letter of the appropriate case before changing the case.

Here is the toLower method.

Character instanceMethod toLower (void) {
  Character new result;

  if (self isAlpha && self isUpper) {
    result = self asInteger ^ 32;
  } else {
    result = self;
  }
  return result;
}

We could omit the asInteger message, but using asInteger, which is implemented in Magnitude class, allows us to reliably check that the receiver of ^ is actually a valid number, even if the receiver of toLower is the result of another statement that translates its value from a completely different receiver elsewhere in the program.

The result of self asInteger is an Integer object, so it uses ^ from Integer class to perform the translation.

The asCharacter method, like asInteger, is defined in Magnitude class.

The value message in the example above is optional. Ctalk sends method arguments as complete objects.

Ctalk statements can appear in many of the places that a C expression can appear. Here is another simple example.

int main () {
  String new ten;
  ten = "10";
  printf ("%d\n", ten asInteger);
}

As a general rule, keep in mind that receiver objects and other methods that are part of complex statements work with objects.

There are many cases, however, when you need to use a message like value in a statement to return an instance variable - generally when you are using objects with C statements, which the next section describes.

The value Message and Interfacing with C

We have already described how refer to objects with C variables in many places. This section provides a slightly more formal definition of how and when to use value.

If you are assigning an object to a C variable, then in most cases you want to use the value message. This message, you might remember from previous chapters, returns the receiver’s value instance variable.

So if you want to assign an object’s value to a C variable, you might use a set of statements like the following.

OBJECT *value_object;
value_object = self value;

You should not, however, simply use an object like self on its own, because Ctalk cannot determine exactly how you plan to use the result. The following example will provide the complete object, which is not what you want.

OBJECT *value_object;
value_object = self;

It is okay, however, to use an object name on its own when used with other objects.

Object new receiverObjectCopy;
receiverObjectCopy = self;

Once you have assigned an object to a C variable, you can treat it as a C struct. The next section describes the OBJECT C typedef.

The OBJECT Type

Ctalk represents objects internally as an OBJECT typedef, so when you need to use an object in a C statement, you can create a C alias for the object by declaring and initializing a C OBJECT *.

Here is the OBJECT type declaration, without some of the machine-specific definitions. It is located in the file classes/ctalklib, and several other places.

struct _object {
  int sig;
  char __o_name[MAXLABEL];
  char __o_classname[MAXLABEL];
  OBJECT *__o_class;
  char __o_superclassname[MAXLABEL];
  OBJECT *__o_superclass;
  OBJECT *__o_p_obj;
  VARENTRY *__o_varentry;
  char *__o_value;
  METHOD *instance_methods,
    *class_methods;
  int scope;
  int nrefs;
  struct _object *classvars;
  struct _object *instancevars;    
  struct _object *next;
  struct _object *prev;
  int attrs;
};

So when a program uses statements like those in the following example, the program is actually creating a C reference to an object.

OBJECT *rcvr_value;
rcvr_value = self value;

Here, = is a C operator. Ctalk evaluates the value message, and then determines that it doesn’t need to perform further translation on the objects because the receiver simply needs the value instance variable as C struct.

Generally, C programs are mainly interested in the __o_value field of an OBJECT *, because this is where objects store their data as C char *’s.

There are also API C functions, like __ctalk_to_c_char_ptr () and __ctalk_to_c_int (), that translate objects to C. The Ctalk Language Reference describes these functions.

Return Values

Methods can return any type of value. Ctalk can usually figure out what how to translate a C language value into a form that it can use internally.

These are all valid return statements.


return "hello!";
return a;
return a + 2;
return TRUE;
return self;
return myObject className;

Occasionally, a method may need to return the result of a complex expression or an expression whose result isn’t known until run time. In that case, you can use the eval keyword to tell Ctalk to wait until the program is run before evaluating the expression, as in this example.

return eval <expression>;

Ctalk treats methods that return arrays a little differently. For a method to return a C array, the array must be explicitly declared; that is, the following definition is translatable into an Array object,

int i[10];

while this statement is not.

int *i;

For a method to return a C array, the method’s definition must specify that it returns an Array class object.

Otherwise, the object might not be directly translatable to a basic Ctalk class, and in that case you should consider assigning the variable to a Symbol object and returning a reference to the variable. The same is true of multi-dimensional arrays, like this one.

int i[10][10];

Again, if a method needs to return a multi-dimensional array, it should consider assigning to to a Symbol object and returning a reference to the method.

The size of a returned Array is the same as the size of the array declared in C, regardless of how the method initializes the C array members. You need to keep that in mind when returning C arrays that may or may not have their members initialized.

Ctalk treats arrays declared as char[size] as String objects, unless the method’s definition says it returns an Array, in which case the method returns an Array with each member being a Character object; otherwise, methods return String objects for char[] declarations.

Occasionally, you may find that you need to write a method that declares a return value’s class explicitly. In these cases, Ctalk also provides a number of method return statements; e.g.; methodReturnInteger and methodReturnTrue. The Ctalk Language Reference contains a complete list of them.

Object References within Methods

In a previous section, we described a few of the principles of object references. See Object References. This section briefly mentions a few aspects of working with actual references within methods.

There are many examples of methods that use object references in the Ctalk class libraries. Here, we’ll mention only a few things to keep in mind when using references in your own methods.

Each member of a collection like an AssociativeArray object, is a Key object. A Key object has two components: a name that is used as a key to look up the object, and a value that is a reference to the object that belongs to the collection. Key objects are a more specialized subclass of Symbol, and the Key class implements several methods to work with Key objects’ labels.

The most important thing to note when working with references, however, is that they can originate anywhere in a program, and your method should not make assumptions about where that object was declared.

However, your method can use the scope of an object to determine how it is used. For example, it can add or remove a VAR_REF_SCOPE or METHOD_USER_OBJECT scope from an object. Object scopes are described more thoroughly in the Ctalk Language Reference.

It’s important to remember that the main point of using object references is that when you change the value of an object, that value changes for every occurrence of that object in a program.

This is often useful for complex objects that persist throughout the life of a program, like Pane objects, which are described below, but it can also cause confusing and seemingly unrelated errors elsewhere in programs if you are not careful.

Just as often, however, you may only need to change the value of an object within a method. In that case, it is often easier and safer to work with a copy of the object. In addition to library functions like __ctalkCreateObjectInit that create objects, Ctalk provides several methods that can alter or duplicate objects. See Variable Promotion and Type Conversion.

Assigning Values

The preceding sections contain a lot of material to digest, and that is at least partially because there can often be several different ways to write a method.

One basic method that many classes implement is an overloaded ‘=’ operator, which Ctalk refers to internally as set_value, setEqual, or another similar identifier.

The function of the ‘=’ method is simple–it sets the value of the receiver object to its argument. However, an assignment operator needs to be able to handle any type of object it is likely to encounter, which makes the method more complicated. The following sections discuss some of these factors.

The ‘=’ Method from Integer Class

One thing objects have in common is that they all have values. Generally, the value of an object is contained in an instance variable named, naturally enough, value. All new objects have a value instance variable, though some classes (like List class) may re-implement its value object(s) differently.

The Object class defines a method, addInstanceVariable, which works nicely for this purpose. So a basic = method could simply use addInstanceVariable.

Integer instanceMethod = set_value (int __intArg) {
  self addInstanceVariable ("value", __intArg value);
  return self;
}

Note that the method declares its argument, __intArg, as an int, not Integer. Ctalk commonly uses C arguments in its basic classes in order to avoid circular object references. (I.e., we couldn’t easily define an Integer object while the Integer class itself is still being defined.)

The method above works fine, assuming that the argument is also an Integer object. However, often you might want to assign a Character, or a LongInteger, or a Float to an Integer object.

Ctalk methods can handle arguments of any class. Internally, it uses the parameter definition to implement basic argument checking, but the definition is mainly a prototype that tells the method what sort of argument to expect, though the parameter definition is not a hard-and-fast rule. Generally, methods can implement their own argument checking when it’s necessary.

Ctalk provides another method, asInteger, defined in Magnitude class, which you can use for the purpose of type conversion. The asInteger method converts other basic scalar types into Integer-compatible values.

If we add an asInteger message, our = method might look like this.

Integer instanceMethod = set_value (int __intArg) {
  self addInstanceVariable ("value", __intArg asInteger value);
  return self;
}

Now we’ll discuss one of the curves in the road. Suppose that __intArg is referred to somewhere else in the program. (It doesn’t matter where else, it could be anywhere else.)

The receiver’s value must be unique within its scope. While it sounds nice in theory to be able to refer to a single object everywhere, the program must still regard scoping rules. (Though there are many classes, like the Pane class and subclasses, where programs need to pass objects by reference in many different places.)

If that’s the case, the method can work on a unique instance of the argument. The method does this with the copy (class Object) method and a local object.

Integer instanceMethod = set_value (int __intArg) {
  Integer new localInt;
  localInt copy __intArg asInteger;
  __ctalkAddInstanceVariable (self, "value", localInt value);
  return self;
}

The copy and become methods (both of them are in Object class) allow you to change objects from one class to another, but there is an important difference. The copy method creates a unique copy of its argument, while become converts the original object into the receiver. (Variable promotion and type conversion are discussed in detail elsewhere. See Variable Promotion and Type Conversion.) So in this case, the = method needs to use the copy message.

Now we need to consider another factor. That is, the class of an object’s value does not need to be the same class as the object itself. This is often the case with instance variables, which belong to the parent object’s class but contain values of a different class.

Let’s return to an earlier example, where we defined a class called PositiveInteger, which could hold only positive numbers. See PositiveInteger Example.

Using the ‘=’ method above, the following program from the earlier section does not work.

Integer class PositiveInteger;
...
int main () {
  Exception new e;
  PositiveInteger new p;

  p = 1;   /* Wrong - p's value becomes an Integer. */

  printf ("%d\n", p + 1);

  printf ("%d\n", p - 2);
  if (e pending)
    e handle;
}

That’s because the PositiveInteger class also defines a - method. So when Ctalk sees the expression ‘p - 2’, it looks for - from Integer class, because p's value is now an Integer. There is no unambiguous way to tell PositiveInteger's value class really isn’t correct, so the only alternative is to redesign the = method to cope with this issue.

A program can deal with this issue by using Ctalk’s library API and work with the object directly in C.

Integer instanceMethod = set_value (int __intArg) { 
  OBJECT *self_alias;
  char buf[MAXLABEL];
  self_alias = self;
  sprintf (buf, "%d", __intArg);
  __ctalkSetObjectValueVar (self_alias, buf);
  return self;
}

Here, we use sprintf(3) and the Ctalk library function __ctalkSetObjectValueVar to format the new value and replace the C object’s value directly. Since the method only replaces the value in the object’s internal structure, the class of the value is unchanged, and the example program above works correctly.

However, this version of = does not have the ability to perform type conversions or handle objects out of scope. As with so much in Ctalk, a more advanced version of this method might combine the two approaches.

Ctalk’s Object class provides yet another method is, that tells you if an object is a member of a certain class. So we’ll use that in the = method to determine if the value of the receiver actually is an Integer.

Integer instanceMethod = set_value (int __i) { 
  OBJECT *self_alias;
  char buf[MAXLABEL];
  Integer new localInt;
  if (self value is Integer) {
    localInt copy __i asInteger;
    __ctalkAddInstanceVariable (self, "value", localInt value);
  } else {
    self_alias = self;
    sprintf (buf, "%d", __i);
    __ctalkSetObjectValueVar (self_alias, buf);
  }
  return self;
}

Further versions of this method need more checking than this. For example, methods frequently need to take into account a receiver’s context, which we’ll discuss below.

In many cases, it’s either convenient or necessary (or both) to tailor a = method to a particular class, as the next section discusses.

The ‘setValue’ Method in Symbol Class

This manual often mentions the fact that programs need to pass objects by reference. The Symbol class handles these chores.

When we want to assign a reference to a Symbol object, we generally mean one of several things.


Symbol instanceMethod setValue (void *v) {
  // Set the value of the receiver to refer to the argument.
  // If the argument is also a Symbol object, set the
  // receiver's value to refer to the same object.
  // Otherwise, set the receiver's value to refer to
  // the argument object.  The referenced object's
  // scope includes VAR_REF_OBJECT scope.
  OBJECT *rcvr_val, *arg;
  char buf[MAXLABEL];

  self removeValue;

  rcvr_val = self value;
  arg = ARG(0);
  if (arg) {
    __ctalkSetObjectScope (arg, arg -> scope | 512);
    if (v is Symbol) {
      __ctalkSetObjectValue (rcvr_val,
			     (arg->instancevars ? 
			      arg->instancevars : arg)->__o_value);
    } else {
#if defined (__GNUC__) && defined (__x86_64) && defined (__amd64__)
      sprintf (buf, "%#lx", (unsigned long int)arg);
#else
      sprintf (buf, "%#x", (unsigned int)arg);
#endif
      __ctalkSetObjectValue (rcvr_val, buf);
    }
  } else {
    /* Needs to evaluate to a NULL C pointer, not a (null) object. */
    __ctalkSetObjectValue (rcvr_val, "0x0");
  }
  return self;
}

Receiver Contexts

In many cases, copying the value of one object to another is good enough. The number 2 and the string “Hello, world!” are the same no matter where they occur.

There are many other cases, however, where you want one object to become another object, or you want a method to refer to the same object using multiple labels, or you want to perform some manipulations on Symbol objects.

In that case, you can write methods that take into account the context the receiver occurs in. This matters mostly with Symbol objects, which need to represent other objects, but you can use the receiver’s context with other objects as well.

In the section Object References we presented an example of how manipulate references to objects. See Object References.

More briefly here, this section discusses the inner workings of the different contexts taken from that example, like these three:


  Symbol new sym1;
  Symbol new sym2;
  Symbol new sym3;
  Integer new i;

  ... 

  sym3 = sym1;

  ... 

  sym1 = i;

  ...

  *sym2 = i;

  ...

This should look a little like C, and in fact it works similarly. The statement, ‘sym3 = sym1’ assigns the object ‘sym1’ to the identifier ‘sym3’.

Likewise, the statement ‘sym1 = i’ assigns the object ‘i’ to the identifer ‘sym1’.

However, the statement ‘*sym2 = i’ creates a reference to ‘i’. ‘sym2’ is still a Symbol that points to an Integer object.

To determine how we want the assignment to behave, we can use the method hasPointerContext (in Object class), which returns True if the object to the left of the = operator has a ‘*’ operator in front of it. If it does, we create a reference to the object. In a non-pointer context, we use the library function __ctalkAliasObject () (or its companion, __ctalkAliasReceiver ()), to assign the argument to the identifier on the left-hand side of the = operator.

The method itself is actually quite simple.


Symbol instanceMethod = setEqual (OBJECT *__newObject) {
  // Alias the argument to the receiver's indentifier. 
  // If the receiver refers to an object reference (it's
  // preceded by a '*' operator), or if the receiver wasn't 
  // declared as a global or local object (e.g., it's an 
  // instance variable), then create a reference to the 
  // argument object using Symbol : setValue .

  if (self hasPointerContext) {
    self setValue __newObject;
  } else {
    if (__ctalkAliasObject (self, __newObject) != 0) {
      self setValue __newObject;
    }
  }

}

You might have noticed, after Ctalk executes a statement like ‘sym1 = i’, that ‘sym1’ is no longer a Symbol object; it’s an Integer.

Basically Ctalk handles this by following the same process in the = method in Integer class as it does for Symbol class. That allows us to use the object’s class to find the right = method; we don’t need to worry nearly as much about type checking when making assignments.

However, if the need arises, you can add type checking to a method. You can do this, simply enough, by adding a check of an object’s class with the is method from Object class.


if (arg is Float) {
   warning ("Assigning a Float object (or value) to an incompatible receiver.");
}

The ‘=’ Method in Integer Class, Revisited

You may have noticed, from the previous examples, that the class of a receiver can change if we assign it a new value. The way that the language’s semantics work is that this is really only an issue when we change from a Symbol, which is a reference, to a which is an actual values, like Integers, Characters, Floats, and so on.

Normally, when you assign, say, a character to an Integer object, the compiler and libraries take care of the conversion from one type to another. When converting from Symbols to other classes, however, Ctalk allows us to add this support in a method.

So, for example, if we want to assign as Symbol object to a receiver that is an Integer (this isn’t so far-fetched), we might want to look at this code again.


  Symbol new sym1;
  Symbol new sym2;
  Symbol new sym3;
  Integer new i;

  ... 

  sym3 = sym1;

  ... 

  sym1 = i;  /* sym1 is now an Integer. */

  ...

  sym1 = sym3;  /* Now we want sym1 to be a Symbol again. */

  ...

In the statement, ‘sym1 = i’, the statement would use the = method from Symbol class. But in the following statement, ‘sym1 = sym3’, Ctalk would use the = method from Integer class.

Fortunately, we can write the Integer = method to support this. Parts of the method are similar to the = method in Symbol class, and parts of it look like the previous Integer = methods. And we can add a convenient warning message if the argument belongs to a class for which the Ctalk libraries don’t normally support type conversions.

Integer instanceMethod = set_value (int __i) { 
  char buf[MAXLABEL];
  if (self hasPointerContext) {
    __ctalkAliasObject (self, __i);
  } else {
    if (__i is Symbol) {
      __ctalkAliasObject (self, __i);
    } else {
      if ((__i value is Integer) || (__i value is LongInteger) || (__i value is Character) || (__i value is Float)) {
	sprintf (buf, "%d", __ctalkToCInteger(ARG(0), 1));
	__ctalkSetObjectValueVar (__ctalk_self_internal (), buf);
      } else {
	_warning 
	("Integer : = : Argument, \"__i,\" class %s is not (yet) supported.\n",
	 __i className);
      }
    }
  }
  return self;
}

Coping with Semantic Issues

It’s up to the method to handle any special semantics that the class’s objects use. For example, the following statements should function equivalently.


String new myString;
Integer new myIntArg;

myIntarg = 123;

myString = "123";             /* These two statements should produce
                                 identical results, even though one argument
                                 is a constant and the other is an expression.*/
myString = myIntArg asString;

Programs generally expect String constants to be assigned to unique objects, so Ctalk provides the __ctalkStringifyName () function to help deal with this semantic peculiarity. There may be others, and it’s up to the method to handle them.


String instanceMethod = setEqual (OBJECT *__stringArg) {
  // String assignment method. Assigns the argument  to the
  // receiver label.  Also does some String-specific
  // semantic stuff for different sorts of String objects.
  // Returns the new String object.

  __ctalkStringifyName (self, __stringArg);
  if (__ctalkAliasReceiver (self, __stringArg) != 0) {
    self addInstanceVariable "value", __stringArg;
    return self;
  } else {
    return __stringArg;
  }
}

Constructor Methods

Constructor methods are the methods that actually create objects. Ctalk provides built-in constructors for basic classes, but they can be (and often are) overridden by constructors in the class libraries

Nearly every program example in this tutorial uses at least one constructor. This section describes constructors in more detail and discusses why and how to write them.

In many object oriented languages, and Ctalk among them, constructor methods are almost always named new. In an application program, their use is simple.

Basic Constructors

Here is an example of a simple constructor.

Integer new myInt;

This statement creates a new object, myInt, that is an instance of Integer class.

The statement performs several tasks. First, it creates the object myInt and makes sure it has the correct links to Integer's class and superclass objects, and sets the initial value to (null).

Secondly, it registers the object and sets its scope depending on where the statement appears. If the statement appears in a method or C function, the object is local to that function, and the name myInt is visible only within that method or function, or code blocks within the method or function. However, if the statement appears outside of a method or function, then the object’s scope is global to the entire program. (To be absolutely correct, though, we should mention that the constructor itself does not set an object’s scope. That happens after the constructor is finished creating the object.)

Thirdly, if the class and its superclasses define instance variables, the constructor creates and initializes instance variables for the new object.

Because classes inherit methods, the constructor used by class Integer is the constructor from Object class. If Integer class defined its own new method, it would use that constructor instead. This point is important and is worth repeating. The process is described in the first section. See Class Hierarchy. Here again, a diagram should help clarify this concept.

Class                  Defines Constructor?    Statement or Action
-----                  --------------------    -------------------
Object                 Yes                     Create object.
                                                    ^
                                                    |
Magnitude              No                      Look up method in superclass.
                                                    ^
                                                    |
Integer                No                      Integer new myInt;
                                               Look up method in superclass.

Initializing Objects

The main reason that a class needs to define a constructor is to initialize whatever data the classes’s objects need.

Suppose we wanted to create Integers that were initialized with the value ‘0’ instead of ‘(null)’. To do this we might define a subclass of Integer. Let’s do that and call the class InitializedInteger.

The first thing we need to do is define the class.

Integer class InitializedInteger;

Then we need to write the constructor for instances of InitializedInteger.

InitializedInteger instanceMethod new (String newObjectName) {
  InitializedInteger super new newObjectName;
  newObjectName = 0;
  return newObjectName;
}

You should take note of the first statement.

InitializedInteger super new newObjectName;

The statement does a lot of things. First, super (which is a keyword, not a method), looks up the new message starting with the receiver’s superclass. It first creates a normal instance of Object class, then promotes it to an instance of InitializedInteger class. After this statement, newObjectName is no longer simply a method parameter, but a fully fledged object.

Now the process looks like this.

Class               Has Constructor?  Statement or Action
-----               ----------------  -------------------
Object              Yes               2. Create Object instance. -> 3. Return Object
                                               ^                    instance to caller.
                                               |                          |
Magnitude           No                    Look up method in superclass.   |
                                               ^                          |
                                               |                          |
Integer             No                    Look up method in superclass.   |
                                               ^                          |
                                               |                          |
InitializedInteger  Yes               InitializedInteger new myInt;       |
                                      1. Look up method in superclass.    |
                                      4. Perform initialization <---------
                                      5. Return object to application.

The class definition and constructor could go in their own class library, but this example is brief enough to include in the main source file. Here is the complete program.

Integer class InitializedInteger;

InitializedInteger instanceMethod new (String newObjectName) {
  InitializedInteger super new newObjectName;
  newObjectName = 0;
  return newObjectName;
}

int main () {
  InitializedInteger new i;
  printf ("%d\n", i);
}

Class Initialization

Another reason you might want to write your own constructor is to initialize a class. This is the case with FileStream subclasses. The program needs to initialize the standard input, standard output, and standard error file handles.

Initializing a class variable is much like initializing an object, except that the class variable only needs to be initialized once. To make sure that a program doesn’t initialize the class every time it creates an object, The class definition needs to include a variable that tells the constructor that class data is already initialized. Here is an abbreviated example from ReadFileStream class, where the constructor new calls the method classInit, which initializes stdin.

FileStream class ReadFileStream;
...
ReadFileStream classVariable stdinStream;
ReadFileStream classVariable classInitDone Integer 0;
...
ReadFileStream classMethod classInit (void) {

  OBJECT *classObject,
    *stdinStreamVar,
    *classInitVar;

  if (self classInitDone)
    return NULL;

  classObject = __ctalkGetClass ("ReadFileStream");
  stdinStreamVar = __ctalkFindClassVariable ("stdinStream", TRUE);
  
  __ctalkObjValPtr (stdinStreamVar, stdin);

  classInitVar = 
    __ctalkCreateObjectInit ("classInitDone", "Integer",
			     "Magnitude",
			     classObject -> scope, "1");
  __ctalkAddClassVariable (classObject, "classInitDone", classInitVar);

  return NULL;
}
...
ReadFileStream instanceMethod new (char *__streamName) {

  ReadFileStream super new __streamName;

  ReadFileStream classInit;
  
  return __streamName; 
} 

These methods contain a lot of C code. There are several reasons for this. The first is that it is often easier, and sometimes necessary, to work directly with operating system functions and data in C.

The second reason is that when working with complex objects, it is possible that the program might contain a circular class reference - when a definition requires a class that isn’t defined yet, and the second class in turn relies on definitions from the first class.

Instance Variable Initialization

If the declaration of the new object contains complex instance variables, especially instance variables that have constructor methods of their own, your constructor needs to construct them also.

For classes that have only a value instance variable, it is generally okay to let Ctalk’s internal object creation routines create a simple object. But constructors do not automatically call other constructors, so simply declaring instance variables of complex classes does not mean they get constructed automatically.

If the constructor needs to create a complex instance variable, it must call that instance variable’s constructor explicitly.

Here’s an example, somewhat abbreviated, from the ANSIScrollingListBoxPane class, which contains several ANSILabelPane instance variables and an ANSIScrollPane instance variable. The constructor creates the variables like normal objects, then adds them to the new object using become (Object class).


ANSIWidgetPane class ANSIScrollingListBoxPane;

  ...

ANSIScrollingListBoxPane instanceVariable selectedContent ANSILabelPane NULL;
ANSIScrollingListBoxPane instanceVariable oldSelectedContent ANSILabelPane NULL;

  ...

ANSIScrollingListBoxPane instanceVariable scrollBar ANSIScrollPane NULL;

  ...

ANSIScrollingListBoxPane instanceMethod new (String __paneName) {

  ANSIWidgetPane super new __paneName;
  ANSIScrollPane new scrollPane;
  ANSILabelPane new selectedContent;
  ANSILabelPane new oldSelectedContent;

    ...

  __paneName scrollBar become scrollPane;

    ...

  __paneName selectedContent become selectedContent;
  __paneName oldSelectedContent become oldSelectedContent;

    ...

  return __paneName;
}

Note that the scrollPane is a local object, while __paneName scrollBar is an instance variable. The become method creates separate objects for the instance variables. The same is true of the selectedContent and oldSelectedContent objects.

This procedure works with objects of any class, whether or not the instance variable’s class is related to the constructor’s receiver class. To create the instance variables from the class’s superclasses, the constructor uses the statement ANSIWidgetPane super new __paneName.

Using an expression like ANSIWidgetPane super new __paneName is a normal way to tell the constructor to instantiate the new object’s instance variables from its superclasses. As this section explained earlier, this is the statement that performs a search back up the class hierarchy and adds the instance variables that each superclass defines.

However, it’s necessary, both for reliability and to avoid serious confusion later in the program, to use super new when the object is first created.

If, for some reason, a program needs to instantiate instance variables from the superclasses elsewhere in a program, the __ctalkInstanceVarsFromClassObject () library function performs the same task. It replaces the previous instance variables with fresh copies. But the program should take care that both the old and new variables are either correctly referenced or disposed of, since the interactions between them can become quite complex.

C Macros for the Method API

Ctalk defines many macros that provide a standard interface to C language features. The macros are defined in the include file ctalkdefs.h. They include macros like IS_OBJECT, which checks if a pointer is a valid object; FMT_0XHEX, which formats a pointer into its string representation, and STR_0XHEX_TO_PTR, which does the converse; when used with a function like sscanf(), it converts the string representation of a pointer into an actual pointer.

To include the definitions in a program, include ctalkdefs.h in a source or class library file.


#include <ctalk/ctalkdefs.h>

The Ctalk Language Reference provides a complete list of the macros that ctalkdefs.h defines.


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