Go to the Next or Previous section, the Detailed Contents, or the Amiga E Encyclopedia.


17.3 Methods in E

The methods of E are very similar to normal procedures, but there is one, big difference: a method is part of a class, so must somehow be identified with the other parts of the class. In E this identification is done by relating all methods to the corresponding OBJECT type for the class, using the OF keyword after the description of the method's parameters. So, the methods of the simple set class would be defined as outlined below (of course, these examples have omitted the code of methods).

PROC add(x) OF set
  /* code for add method */
ENDPROC

PROC member(x) OF set
  /* code for member method */
ENDPROC

PROC empty() OF set
  /* code for empty method */
ENDPROC

PROC union(s:PTR TO set) OF set
  /* code for union method */
ENDPROC

At first sight it might seem that the particular set object which would be manipulated by these methods is missing from the parameters. For instance, it appears that the empty method should need an extra PTR TO set parameter, and that would be the set object it tested for emptiness. However, methods are called in a slightly different way to normal procedures. A method is a part of a class, and is called in a similar way to accessing the data elements of the class. That is, the method is selected using `.' and acts (implicitly) on the object from which it was selected. The following example shows the allocation of a set object and the use of some of the above methods.

  DEF s:PTR TO set
  NEW s  -> Allocate an OOP object
  s.add(17)
  s.add(-34)
  IF s.empty()
    WriteF('Error: the set s should not be empty!\n')
  ELSE
    WriteF('OK: not empty\n')
  ENDIF
  IF s.member(0)
    WriteF('Error: how did 0 get in there?\n')
  ELSE
    WriteF('OK: 0 is not a member\n')
  ENDIF
  IF s.member(-34)
    WriteF('OK: -34 is a member\n')
  ELSE
    WriteF('Error: where has -34 gone?\n')
  ENDIF
  END s  -> Finished with s now

This is why the methods do not take that extra PTR TO set argument. If a method is called then it has been selected from an appropriate object, and so this must be the object which it affects. The slightly complicated method is union which adds another set object by unioning it. In this case, the argument to the method is a PTR TO set, but this is the set to be added, not the set which is being expanded.

So, how do you refer to the object which is being affected? In other words, how do you affect it? Well, this is the remaining difference from normal procedures: every method has a special local variable, self, which is of type PTR TO class and is initialised to point to the object from which the method was selected. Using this variable, the data and methods of object can be accessed and used as normal. For instance, the empty method has a self local variable of type PTR TO set, and can be defined as below:

PROC empty() OF set IS self.size=0

Constructors are simply methods which initialise the data of an object. For this reason they should normally be called only when the object is allocated. The NEW operator allows OOP objects to call a constructor at the point at which they are allocated, to make this easier and more explicit. The constructor will be called after NEW has allocated the memory for the object. It is wise to give constructors suggestive names like create and copy, or the same name as the class. The following constructors might be defined for the set class:

/* Create empty set */
PROC create() OF set
  self.size=0
ENDPROC

/* Copy existing set */
PROC copy(oldset:PTR TO set) OF set
  DEF i
  FOR i:=0 TO oldset.size-1
    self.elements[i]:=oldset.elements[i]
  ENDFOR
  self.size:=oldset.size
ENDPROC

They would be used as in the code below. Notice that the create constructor is, in this case, redundant since NEW will initialise the data elements to zero. If NEW does sufficient initialisation then you do not have to define any constructors, and even if you do have constructors you don't have to use them when allocating objects.

  DEF s:PTR TO set, t:PTR TO set, u:PTR TO set
  NEW s.create()
  IF s.empty THEN WriteF('s is empty\n')
  END s
  NEW t  /* This happens to be the same as using create */
  IF t.empty THEN WriteF('t is empty\n')
  t.add(10)
  NEW u.copy(t)
  IF u.member(10) THEN WriteF('10 is in u\n')
  END t, u

For each class there is at most one destructor, and this is responsible for clearing up and deallocating resources. If one is needed then it must be called end, and (as this might suggest) it is called automatically when an OOP object is deallocated using END. So, for OOP objects with a destructor, the (roughly) equivalent code to END using Dispose is a bit different. Take care to note that the destructor is not called if END is not used to deallocate an OOP object (i.e., if deallocation is left to be done automatically at the end of the program).

  END p

  IF p
    p.end()  -> Call destructor
    Dispose(p)
    p:=NIL
  ENDIF

The simple implementation of the set class needs no destructor. If, however, the elements data were a pointer (to LONG), and the array were allocated based on some size parameter to a constructor, then a destructor would be useful. In this case the set class would also need a maxsize data element, which records the maximum, allocated size of the elements array.

OBJECT set
  elements:PTR TO LONG
  size
  maxsize
ENDOBJECT

PROC create(sz=100) OF set  -> Default to 100
  DEF p:PTR TO LONG
  self.maxsize:=IF (sz>0) AND (sz<100000) THEN sz ELSE 100
  self.elements:=NEW p[self.maxsize]
ENDPROC

PROC end() OF set
  DEF p:PTR TO LONG
  IF self.maxsize=0
    WriteF('Error: did not create() the set\n')
  ELSE
    p:=self.elements
    END p[self.maxsize]
  ENDIF
ENDPROC

Without the destructor end, the memory allocated for elements would not be deallocated when END is used, although it would get deallocated at the end of the program (in this case). However, if AllocMem were used instead of NEW to allocate the array, then the memory would have to be deallocated using FreeMem, and this would best be done in the destructor, as above. (The memory would not be deallocated automatically at the end of the program if AllocMem is used.) Another solution to this kind of problem would be to have a special method which called FreeMem, and to remember to call this method just before deallocating one of these objects, so you can see that the interaction of END with destructors is quite useful.

Already, the above re-definition of set begins to show the power of OOP. The actual implementation of the set class is very different, but the interface can remain the same. The code for the methods would need to change to take into account the new maxsize element (where before the fixed size of 100 was used), and also to deal with the possibility the create constructor had not been used (in which case elements would be NIL and maxsize zero). But the code which used the set class would not need to change, except maybe to allocate more sensibly sized sets!

Yet another, different implementation of a set was outlined above (see 16.3 Binary Trees). In fact, remarkably few changes would be needed to convert the code from that section into another implementation of the set class. The new_set procedure is like a set constructor which initialises the set to be a singleton (i.e., to contain one element), and the add procedure is just like the add method of the set class. The only slight problem is that empty sets are not modelled by the binary tree implementation, so it wouldn't, as it stands, be a complete implementation. It would be straight-forward (but unduly complicated at this point) to add support for empty sets to this particular implementation.


Go to the Next or Previous section, the Detailed Contents, or the Amiga E Encyclopedia.