Object variables

From m204wiki
Jump to navigation Jump to search

An object variable is a proxy or substitute for an object. You do not access or perform actions against an object directly but instead via operations on an object variable that references the object. This page discusses how you create, work with, and discard object variables.

Why use an object reference instead of an object

Object variables are used to declare variables that refer to instances of a class. Despite their name, they are not really objects, as such, but references or pointers to objects. The underlying objects themselves are never directly accessible and can only be accessed via an object reference. This concept of reference variables being used to access underlying objects is common to all object-oriented languages. There are two, primary reasons for this:

  1. Objects can get quite large, and moving them around an application rather than moving references around would be extremely expensive.
  2. Objects usually represent some "real world" entity. To have multiple copies of such an entity in an application creates the possibility that these copies would get out of synch, causing application complexity and even bugs.

Assignment and comparison operations are probably the two most significant places to note the distinction between object variables being references to the objects or the objects themselves. The following is an example of assignment of an object reference, rather than an object:

class pet public variable type is string len 16 end public end class ... %rover is object pet %fluffy is object pet ... %rover:type = 'Dog' %fluffy = %rover %fluffy:type = 'Cat' print %rover:type

Because the statement %fluffy = %rover assigns to %fluffy a reference to the same object as %rover, both variables end up referring to the same object. The statement %fluffy:type = 'Cat' therefore changes the underlying object pointed to by %rover (since it's the same object as the one pointed to by %fluffy). As a result, the Print statement prints Cat.

As assignment assigns references, comparison compares references. That is, a comparison between two object references does not compare the objects being referred to, but it instead compares whether two object references point to the same object.

Consider the following example::

class pet public variable type is string len 16 end public end class ... %rover is object pet %fluffy is object pet ... %rover = new %fluffy = new %rover:type = 'Dog' %fluffy:type = 'Dog' if %fluffy eq %rover then print 'They''re equal!' end if

This will not print They're equal. Even though the objects pointed to by %fluffy and %rover have the same variable values, they are different objects, so %fluffy is not equal to %rover. However, if the code were changed:

class pet public variable type is string len 16 end public end class ... %rover is object pet %fluffy is object pet ... %rover:type = 'Dog' %rover = %fluffy if %fluffy eq %rover then print 'They''re equal!' end if

They're equal will be printed, because %rover = %fluffy sets %rover to point to the same object as %fluffy, making them equal.

Note: Since order comparisons (GE, LE, GT, and LT) are meaningless for references — when is a reference to one object greater than a reference to another? — only equality and inequality (EQ and NE) comparisons are allowed for objects.

Despite the foregoing remarks, for most purposes, one can still think of object variables as being the objects themselves, rather than references to the object. For example, in the statement:

%rover:type = 'Ferret'

it is quite natural to think of the statement as setting %rover's Type to "ferret," rather than as setting the type of the object referenced by %rover to "Ferret."

Creating object instances

Since object variables are references to objects, two questions naturally arise: what object does an object variable refer to initially, and where does the referenced object come from? The answer to the first question is that an object variable initially points to no object. So, the following program would cause a request cancellation error:

class pet public variable type is string len 16 end public end class ... %rover is object pet %rover:type = 'Dog'

The statement %rover:type = 'Dog' attempts to set the object's type to "Dog," but there is no instance of an object for which to set the type. This kind of error is sometimes referred to as a null pointer exception.

So how, then, is an object created? The answer is via a Constructor, usually the default one named New.

Using New or other Constructors

When you invoke a Constructor method, SOUL creates an object instance which is available to the Constructor code. The Constructor:

  • Operates on the new instance (which is the value of %this within the Constructor)

    Unlike a shared method, a Constructor operates on an object instance.

  • Returns the value of %this as the result.

    Like a Subroutine, the Constructor's Return statement may not specify a value, but like a Function, the Constructor does produce a result.

In any non-Abstract class, you can invoke a default Constructor, named New, which operates no further on %this and simply returns a new instance for explicit or implicit assignment. As described below, you can explicitly define New to operate on the new instance, you can define other constructors in addition to New, and you can also "disallow" New from use outside the class definition.

Syntax for Constructors

The syntax for invoking a Constructor is the same as the syntax for invoking a Shared method which returns an instance of the class.

To invoke the New constructor and assign its result:

%rover = new

You can explicitly indicate the class of the object as follows:

%rover = %(pet):new

While specifying the class in this way is unnecessary and might be viewed as detrimental because it results in the class name being sprinkled around inside application code, it might also be viewed as beneficial — making the object class clear in the part of code where the object is created.

Customizing a Constructor

Sometimes it is useful to run code when creating an instance of a class. In such cases, you can explicitly define the New Constructor:

class pet public variable initTime is float constructor new end public constructor new %initTime = $sir_datens end constructor end class

In the above example, the class variable initTime is set to the time the instance of the object is created. That code runs when the New Constructor is invoked in a program to create a pet instance:

%josh is object pet ... %josh = new

Sometimes it is useful to have a constructor take parameters. For example, if it doesn't make sense to have a Pet object without a Type, make the New constructor require an input parameter that sets the class Type variable:

class pet public variable type is string len 16 constructor new(%type is string len 16) end public constructor new(%type is string len 16) %this:type = %type end constructor end class

The parameters must be specified on the New constructor:

%snoopy is object pet ... %snoopy = new('Beagle')

As of Sirius Mods Version 7.2, constructors are allowed to change the object being constructed. For example, variable ExistingObj assigns an existing Dachsie object when constructor Newbie is called:

class dachsie public ... constructor newbie ... end public public shared variable ExistingObj is object dachsie end public shared constructor newbie %this = %(this):existingObj end constructor ... end class

However, this feature was actually provided for a different purpose, and using it in the context shown above requires caution. While %this is an input parameter to the Constructor, changing it changes the object returned to the Constructor invoker, so it acts almost like an output parameter. Modifying %this within the Constructor can also produce unexpected behavior if the Constructor is itself called from an extension class Construct statement.

Instead of using a Constructor to return a modified object as in the example above, you should consider using a "factory method".

Other ways to use a Constructor

A Constructor can also be used in these ways:

  • To specify a new instance of a class as an input parameter to a method:

    class pet public ... subroutine compare(%pet is object pet) ... end public ... end class ... %lassie is object pet ... %lassie:compare( new('Collie') )

  • Without being explicitly specified, to automatically create an instance of an object when an object variable is first referenced. For such instantiation, you must specify Auto New in the object's declaration.

Using multiple Constructors

Most object-oriented languages support only a single constructor name for a class. If multiple constructors are needed, this is achieved with overloading — the use of different methods with the same name that are distinguished by their parameter lists. Overloading, however, can be confusing, and it is limited: you can't have multiple methods with the same name and the same datatypes in the parameter list.

SOUL supports multiple constructors:

class resource public constructor newFromUrl(%url is string len 255) constructor newFromProc(%proc is string len 255) ... end public ... end class ... %res is object resource ... %res = newFromUrl('http://nfl.com/score') ... %res = newFromProc('LOCAL.RESOURCE.PROC') ... %res = new

This example illustrates:

  • Constructors not called New are invoked in exactly the same way as the New constructor.

    A function that invokes a constructor can be used in any other context in which the New Constructor can be used.

  • It is recommended that you always begin Constructor names with the word "New" to distinguish them from other methods.

Private Constructors

Although this section has discussed only public constructors, that is, constructors defined for use outside the class definition, you may also define private constructors, which may only be invoked from inside the class. You declare such a Constructor in the Private block of a class definition, and you define it with your other methods after the declaration blocks.

If you want to define a private constructor named New, however, you must make sure to specify Disallow New in the Public block of the class definition (see "Declaration block syntax"). No other private constructors require an additional Disallow New in the Public block.

Virtual constructors

It is possible to have a non-constructor shared function return an instance of a class:

class resource public shared function newFromUrl(%url is string len 255) - is object resource ... end public shared function newFromUrl(%url is string len 255) - is object resource %return is object resource print 'In the factory' %return = new end function ... end class ... %res is object resource ... %res = %res:newFromUrl('http://nfl.com/score')

Such functions are often called factory methods or virtual constructors. As the example illustrates, two things distinguish factory methods from constructors:

  • They can run code before the object is instantiated.
  • They might not actually instantiate an object but return a reference to an existing instance. Such a method may not truly be a factory method.

The support for multiple constructors means that SOUL users need factory methods less than those using most other object-oriented programming languages.

Virtual constructors can be invoked in much the same way that regular constructors are invoked, that is, without specifying the class name if the class name can be determined from context. For example, the above example of the NewFromUrl virtual constructor can be changed to the following:

%res is object resource ... %res = newFromUrl('http://nfl.com/score')

Similarly, if a method called Send in class Coworker took a resource object as an input parameter, the NewFromUrl virtual constructor could also be used as follows:

%sufjan is object coworker ... %sufjan:send(newFromUrl('http://asthmatickitty.com'))

Because the naming rules for virtual constructors are now identical to that for regular constructors, the two can be used interchangeably, and one can be changed to the other as requirements change, without breaking existing applications.

Object variables within class definitions

Classes can, themselves, contain object variables:

class pet public ... variable owner is object human ... end public ... end class

Remember that an object variable is not really the object itself but a reference to an underlying object so, in the case of an object variable inside a class block, the class does not contain another object, per se, but simply a reference to another object.

It is possible for two different classes to each contain references to each other. But for an object variable to be defined, the object class must already have been declared. So if two classes contain references to each other, at least one must be declared without the references before the other. The simplest way to do this is with an empty Class block:

class human end class class pet public ... variable owner is object human ... end public ... end class ... class human public ... variable dog is object pet ... end public ... end class

It is also possible for a class to contain object variables that refer to the containing class:

class pet public ... variable sibling is object pet ... end public ... end class

In such a case, no empty Class block is necessary to declare the class because the Class block containing the object variable is sufficient to declare the existence of the class.

Discarding objects

The New constructor creates an instance of a class. This instance is maintained until it is either implicitly or explicitly discarded (or destroyed in the terminology of some object-oriented languages).

Discarding explicitly

An underlying object can be discarded explicitly by invoking the Discard method (system subroutine) against it:

%mick is object dog ... %mick = new('Terrier') ...

  • Done with object pointed to by %mick


The Discard method never takes any input parameters and returns no value, so it is a Subroutine. The Discard method is an AllowNullObject method because it can be invoked with a null object, such as one that was never set:

%mick is object dog for %i from 1 to 10 %mick:discard ... end for

In the first loop iteration, above, %mick would not be set, so the %mick:discard call would not have anything to discard, but it would not indicate a request-cancelling null-pointer error.

Note: Not all objects may be discarded explicitly:

  • Some objects are integral parts of other objects, and discarding one cannot be done without compromising the other. For example, you may not explicitly Discard an XmlNode object, though as discussed below, you may discard it by using the DeepDiscard method.
  • Some objects may be explicitly defined with a Disallow Discard clause, which protects them from being discarded directly.

Discarding implicitly

Objects may be discarded implicitly, when there are no more references to the underlying object. If there are no references to an underlying object, there is no way to retrieve or set values in the object or to invoke methods against the object. Since there is no reason to maintain the object any more, the Janus SOAP User Language Interface automatically discards the object. The following code demonstrates this principle:

%bunny is object dog ... %bunny = new('Ibizan Hound') %bunny:show %bunny:feed %bunny = new('Yorkshire Terrier')

When the statement %bunny = new('Yorkshire terrier') is executed, a new object is created, but it is no longer possible to access the object instantiated by %bunny = new('Ibizan hound') — there are no longer any references to that object. Because of that, the first Dog object is automatically discarded when the second Dog object is created and referenced by %bunny. If a second reference to that object had been set, it would not have been discarded:

%bunny is object dog %dallas is object dog ... %bunny = new('German Shepherd') %dallas = %bunny %bunny:show %bunny:feed %bunny = new('Yorkshire terrier')

A reference to an object can be inside another object:

class dog public variable name is string len 32 variable breed is string len 32 variable nextCompetitor is object ... end public end class ... %first is object dog ... %first = new('Josh', 'Newfoundland') %first:nextCompetitor = new('Mick', 'Kerry Blue Terrier')

In the above example, the object created by new('Mick', 'Kerry Blue Terrier') can be accessed via %first:nextCompetitor, so it will not be implicitly discarded. In such a way, it is possible to build a chain of objects:

%first is object dog %last is object dog %new is object dog ... %new = new('Josh', 'Newfoundland') %first = %new %last = %new %new = new('Mick', 'Kerry Blue Terrier') %last:nextCompetitor = %new %last = %new %new = new('Les', 'Pekingese') %last:nextCompetitor = %new %last = %new %new = new('Bunny', 'Ibizan Hound') %last:nextCompetitor = %new %last = %new

In the above example, the object created by new('Les', 'Pekingese') can be accessed by %first:nextCompetitor:nextCompetitor, so it is not implicitly discarded.

By linking objects in such a way, it is possible to create a wide variety of object super-structures, including chains, trees, webs, and cycles. A cycle is a collection of objects where the objects are linked in such a way as to form a ring:

%first is object dog %last is object dog %new is object dog ... %new = new('Josh', 'Newfoundland') %first = %new %last = %new %new = new('Mick', 'Kerry Blue Terrier') %last:nextCompetitor = %new %last = %new %new = new('Les', 'Pekingese') %last:nextCompetitor = %first

A cycle can even be created with a single object:

%new is object dog ... %new = new('Josh', 'Newfoundland') %new:nextCompetitor = %new

A web of objects can contain many cycles of objects.

If a cycle of objects is created, each object in the cycle will always have at least one reference to it — the previous item in the cycle. This means that even if all other references to the objects in a cycle were eliminated, the objects would still not be discarded because they would each still have a reference to them, even though those references, themselves, aren't reachable from any variables in the request. The following illustrates a case where two objects cannot be reached via any %variable, but they won't be discarded because they reference each other:

%first is object dog %last is object dog %new is object dog ... %first = new('Josh', 'Newfoundland') %new = new('Mick', 'Kerry Blue Terrier') %first:nextCompetitor = %new %new:nextCompetitor = %first %new = new('Les', 'Pekingese') %first = %new

Such objects will not be discarded by SOUL until the user logs off. Most object-oriented languages solve this problem with garbage collection — a process in which all objects are scanned to determine if they are "reachable" from some base variables (in SOUL these would be local and common %variables).

SOUL has a DeepDiscard method, which provides an alternative approach to some of these cases that is less expensive than garbage collection and simpler than Discard. DeepDiscard is discussed in the next section.

Deep discard of objects

The Discard method (Object class subroutine) is usually available to explicitly discard the object referenced by an object variable. If an object contains a reference to another object (of the same or a different class), an explicit Discard results in the elimination of the reference. This removal of the reference can result in an implicit discard of the reference object, as shown in the following example.

The object referenced by %chain contains a reference to another object. When %chain is discarded, the reference to the object with the name Frack is lost. Since this is the only reference to that object, the object is then implicitly discarded.

class linkedList public variable name is string len 32 variable next is object linkedList end public end class ... %chain is object linkedList %chain = new %chain:name = 'Frick' %chain:next = new %chain:next:name = 'Frack' %chain:discard

Now suppose the class definition remains the same, but the code is changed to the following:

%chain is object linkedList %link is object linkedList %chain = new %chain:name = 'Frick' %chain:next = new %chain:next:name = 'Frack' %link = %chain:next %chain:discard

In this case, even when the object referenced by %chain is discarded, and its reference to the Frack object goes away, there is still a reference to the Frack object, namely, %link, so that object is not discarded.

SOUL also has a generic DeepDiscard method:


Like the Discard method, DeepDiscard explicitly discards the method object. But, unlike Discard, it also explicitly discards all objects that are referenced directly or indirectly (a reference to an object that contains a reference to another object, and so on) by the object being discarded. This makes DeepDiscard useful for discarding an entire chain of objects, an object tree, an object cycle, or even an object forest, even if there are still references to some of the objects in the request.

Since DeepDiscard cleans up an object cycle or forest, it is an efficient alternative to having garbage collection clean these up, if there is an obvious point in code where the object cycle or forest is no longer used.

Just as with the Discard method, there is a danger with DeepDiscard that some reference to an explicitly discarded object might still be required. But because it has a potential to discard more objects, the risk is somewhat greater with DeepDiscard, so it should be used with caution.

The DeepDiscard method cannot be used on any object in a class that

  • is declared with a Disallow Discard clause
  • contains a reference to a class defined with Disallow Discard
  • contains an indirect reference to a class that is defined with Disallow Discard

Without these rules, an explicit DeepDiscard of an object could result in the explicit discard of an object of a class that expressly disallows explicit discards.

For most system classes, DeepDiscard works identically to Discard. The exceptions to this are:

  • Screenfield objects, which you may DeepDiscard but not Discard. If you DeepDiscard a Screenfield, the underlying Screen object is also discarded.
  • XmlNode objects, which you may DeepDiscard but not Discard. If you DeepDiscard an XmlNode, the underlying XmlDoc object is also discarded.
  • XmlNodelist objects, which you may Discard or DeepDiscard. If you Discard an XmlNodelist, the XmlNodelist object (only) is discarded. If you DeepDiscard an XmlNodelist, both the XmlNodelist object and the underlying XmlDoc object are discarded.

Working with null valued object variables

If an object variable does not refer to an object, either because it was never set, or because the object to which it referred was discarded, it is considered to have a Null value. While, an object variable with a Null value cannot be used as the object for most methods (except those declared with AllowNullObject), they can be used in comparisons.

The most common comparison done with objects, in fact, is to test whether the object is Null:

%first is object dog %new is object dog ... %new = new('Josh', 'Newfoundland') if %first eq null then %first = new else %last:nextCompetitor = %new end of %last = new

If a null object variable is compared with another object variable, it is considered equal if, and only if, the other object variable is null.

A null object variable can be assigned to another object variable:

%first is object dog %new is object dog %new = %first

And a variable can be explicitly set to Null:

%winner is object dog ... %winner = null

In both comparisons and assignments, Null is actually a shorthand for a shared null property on the class, and it can be indicated as such:

%winner is object dog ... %winner = %(dog):null ... if %winner eq %(dog):null ...

The value Null can also be passed as an input parameter to a method:

class show public ... function score(%entrant is object dog allowNull) - is float ... end public ... end class ... %winner is object dog %akc is object show ... %minScore = %akc:score(null) ...

Note: Assigning a value to an object property, that is to a Property that returns an object, is a special case of passing an input parameter to a method. The value assigned is an implicit parameter of the Set method of the property. In such cases, you may validly assign a Null value, as if the implicit parameter included an implicit AllowNull keyword.

An object that is explicitly discarded causes all references to that object to become null:

%first is object dog %new is object dog %new = new('Dallas', 'German Shepherd') %first = %new %first:discard

In the above example, both %first and %new are set to Null after the %first:discard.

Passing object variables as parameters

Object variables can be passed as method or complex subroutine parameters:

class clown public subroutine honk(%nose is object nose) ... end public ... end class ... %bozo is object clown %schnoz is object nose ... %bozo:honk(%schnoz)

By default, passing a null object variable as a method or complex subroutine parameter causes request cancellation. However, the AllowNull keyword can be specified as an Input parameter qualifier in the method or complex subroutine declaration to indicate that the method or complex subroutine will accept null objects:

class clown public subroutine honk(%nose is object nose allowNull) ... end public ... end class

An Output object variable is always allowed to be null and can be set by a method:

class clown public subroutine honk(%nose is object nose output) ... end public subroutine honk(%nose is object nose output) %nose = new('red') end subroutine ... end class ... %bozo is object clown %schnoz is object nose ... %bozo = new %bozo:honk(%schnoz)

In this example, %schnoz would be set to reference a new instance of a nose object that was instantiated by the honk method.

An Input object variable is passed by value to a method and may be modified within the method. The method actually receives a copy of a pointer to the object. The copied pointer may be used to modify the referenced object, and the modifications are propagated to the object outside the subroutine. Changes to the pointer like assignment or new instantiation, are allowed, but they are not propagated to the outer object.

In the example below, the subroutine modifies the %schnoz object color, but the reassignment to %schnozola does not affect %schnoz:

class clown public subroutine honk(%nose is object nose output) ... property color is string ... end public subroutine honk(%nose is object nose output) %schnozola is object nose If %clown eq %bozo 'red' = %nose:color ... %nose = %schnozola ... end subroutine ... property color is string ... end property color end class ... %bozo is object clown %schnoz is object nose ... %bozo = new %bozo:honk(%schnoz)

Note: A case where a method significantly affects the object referenced by an Input object variable is the Discard method.

A Discard method invoked against an Input parameter sets the Input parameter and call argument to Null.

This can happen directly:

subroutine honk (%nose is object nose input) %nose:discard end subroutine

Or, it can happen indirectly:

subroutine honk (%nose is object nose input) %beak is object nose %beak = %nose %beak:discard end subroutine

See also