Classes and Objects: Difference between revisions
(5 intermediate revisions by 3 users not shown) | |||
Line 46: | Line 46: | ||
%chevy:paint('Teal') | %chevy:paint('Teal') | ||
</p> | </p> | ||
Subroutine <code>Paint</code> can only be defined '''after''' it has been declared inside a [[#Declaration blocks|declaration block]] for class <code>Car</code>. If a class is broken up in such a way that some methods are in their own procedures, it is likely that the class's declaration blocks would all be contained in another procedure, called something like <code>CLASS.CAR</code> in this example. | Subroutine <code>Paint</code> can only be defined '''after''' it has been declared inside a [[#Declaration blocks|declaration block]] for class <code>Car</code>. If a class is broken up in such a way that some methods are in their own procedures, it is likely that the class's declaration blocks would all be contained in another procedure, called something like <code>CLASS.CAR</code> in this example. It would be necessary to <var>Include</var> that procedure '''before''' the procedure that defines the <code>Paint</code> method: | ||
<p class="code">include class.car | <p class="code">include class.car | ||
include class.car.paint | include class.car.paint | ||
Line 79: | Line 79: | ||
<td>Indicates that the class is '''abstract''', that is, no instance of the class can be created unless it is an instance of a non-abstract extension class.</td></tr> | <td>Indicates that the class is '''abstract''', that is, no instance of the class can be created unless it is an instance of a non-abstract extension class.</td></tr> | ||
<tr><th><var>Alias=</var>aliasList</th> | <tr id="aliasList"><th><var>Alias=</var>aliasList</th> | ||
<td>A list of aliases for the class, with each alias separated by the word <var>And</var>. See [[#Aliases for class names|Aliases for class names]].</td></tr> | <td>A list of aliases for the class, with each alias separated by the word <var>And</var>. See [[#Aliases for class names|Aliases for class names]].</td></tr> | ||
Line 128: | Line 128: | ||
===Aliases for class names=== | ===Aliases for class names=== | ||
As of <var class="product">Sirius Mods</var> Version 7.8, you can define | As of <var class="product">Sirius Mods</var> Version 7.8, you can define one or more alias names for an existing user class. | ||
You do so by specifying an <var>Alias</var> | You do so by specifying an <var>[[#aliasList|Alias]]</var> list in the class declaration. For example, | ||
you are changing a naming convention for some code, and you want to refer to user-defined class <code>Blue</code> by another name, say <code>Indigo</code>. Your class declaration would be: | you are changing a naming convention for some code, and you want to refer to user-defined class <code>Blue</code> by another name, say <code>Indigo</code>. Your class declaration would be: | ||
<p class="code">class blue alias indigo | <p class="code">class blue alias indigo | ||
Line 149: | Line 149: | ||
</p> | </p> | ||
While this might seem potentially confusing, aliases are intended primarily for migrating class names, so any confusion will be limited to this migration period. In addition, only the owner of a class can declare an alias, so aliases are not likely to proliferate in your site's code. | While this might seem potentially confusing, aliases are intended primarily for migrating class names, so any confusion will be limited to this migration period. In addition, only the owner of a class can declare an alias, so aliases are not likely to proliferate in your site's code. | ||
Multiple aliases in the alias list are separated by the keyword <var>And</var>.<!--SOUL.DME.278.QA has an example of multiple aliases, probably don't need to belabor it here in the doc--> | |||
==Declaration blocks== | ==Declaration blocks== | ||
Line 347: | Line 349: | ||
===Variable declarations=== | ===Variable declarations=== | ||
Variable declarations in a class declaration block must begin with <var>Variable</var> followed by the variable name (without a percent sign). This is followed by any standard <var class="product"> | Variable declarations in a class declaration block must begin with <var>Variable</var> followed by the variable name (without a percent sign). This is followed by any standard <var class="product">SOUL</var> variable declaration, with the exception that: | ||
<ul> | <ul> | ||
<li><var>Static</var> is not valid in a non-Shared block. | <li><var>Static</var> is not valid in a non-Shared block. </li> | ||
<li><var>Global</var> is not valid in a non-Shared block and requires the global name in a Shared block. | |||
<li><var>Common</var> is not allowed. | <li><var>Global</var> is not valid in a non-Shared block and requires the global name in a Shared block. </li> | ||
<li>The keywords to indicate "publicity" (<var>Private</var>, <var>Private Shared</var>, <var>Public</var>, and <var>Public Shared</var>) are allowed. If specified, they must match the publicity of the enclosing declaration block. | |||
<li><var>Auto</var> followed by <var>New</var> or an enumeration value is allowed. | <li><var>Common</var> is not allowed. </li> | ||
<li><var>ReadOnly</var> and <var>Protected</var> | |||
<li>The keywords to indicate "publicity" (<var>Private</var>, <var>Private Shared</var>, <var>Public</var>, and <var>Public Shared</var>) are allowed. If specified, they must match the publicity of the enclosing declaration block. </li> | |||
<li><var>Auto</var> followed by <var>New</var> or an enumeration value is allowed. </li> | |||
<li>For <var>ReadOnly</var> and <var>Protected</var> variable declarations, like standard <var class="product">Model 204</var> variable declarations, the keyword <var>Is</var> immediately after the variable name on variable declarations is optional. </li> | |||
</ul> | </ul> | ||
====Variable declaration examples==== | |||
<p> | |||
An example follows of a class block with some variable declarations: | An example follows of a class block with some variable declarations: | ||
</p> | |||
<p class="code">class car | <p class="code">class car | ||
public | public | ||
Line 366: | Line 376: | ||
end class | end class | ||
</p> | </p> | ||
Class variables can be referenced | |||
====Variable declaration references==== | |||
Class variables can be referenced with an instance of that class, that is, | |||
with an ''object variable,'' like <code>%gremlin</code> below: | |||
<p class="code">%gremlin is object car | <p class="code">%gremlin is object car | ||
... | ... | ||
printText The price is {%gremlin:price} | printText The price is {%gremlin:price} | ||
</p> | </p> | ||
The colon (< | The colon (<tt>:</tt>) that separates the object variable <code>%gremlin</code> and the class member variable <code>price</code>, above, may alternatively be specified with a one or more blanks before and/or after it. | ||
and the class member variable <code>price</code>, above, may alternatively be | |||
specified with a one or more blanks before and/or after it. | |||
It is possible to to declare a variable | ====ReadOnly and Protected variables==== | ||
as <var>ReadOnly</var> or <var>Protected</var>. | It is possible to to declare a variable as <var>ReadOnly</var> or <var>Protected</var>. | ||
A <var>ReadOnly</var> variable can be examined by code outside the class, but it can only | A <var>ReadOnly</var> variable can be examined by code outside the class, but it can only | ||
be updated inside the class. | be updated inside the class. | ||
Line 392: | Line 402: | ||
Now, suppose this class wants to allow extension classes to set a different <code>PartNumber</code>, probably in their constructors. | Now, suppose this class wants to allow extension classes to set a different <code>PartNumber</code>, probably in their constructors. | ||
One approach would be to make <code>PartNumber</code> an <var>Overridable</var> <var>ReadOnly</var> property. | One approach would be to make <code>PartNumber</code> an <var>Overridable</var> <var>ReadOnly</var> property. | ||
But this is a somewhat heavyweight approach, as it requires a bit of code (the property <var>Get</var> method) for each extension class, and this code has to be run every time PartNumber is retrieved. | But this is a somewhat heavyweight approach, as it requires a bit of code (the property <var>Get</var> method) for each extension class, and this code has to be run every time <code>PartNumber</code> is retrieved. | ||
Instead, <code>PartNumber</code> could be made a <var>Protected</var> variable, and an extension class's constructor could simply set it after calling the base class constructor. | Instead, <code>PartNumber</code> could be made a <var>Protected</var> variable, and an extension class's constructor could simply set it after calling the base class constructor. | ||
This allows the extension classes to override the base class's <code>PartNumber</code> using little extra code and using a very efficient access path for the value. | This allows the extension classes to override the base class's <code>PartNumber</code> using little extra code and using a very efficient access path for the value. |
Latest revision as of 04:50, 18 June 2019
Classes
A class is a collection of attributes that describe an object and of operations that may be performed on an object. The attributes of an object are formally declared as class variables, while the operations are declared as methods. In SOUL, there are four types of methods: subroutines, functions, properties, and constructors. While a class describes general attributes and methods, a specific instance of the class is called an object, and variables that refer to those instances are called object variables.
The description of a class, all variables, and all methods of the class must be contained inside one or more Class blocks:
begin ... class car ... end class ... class car ... end class car ... end
This is slightly different from most other object-oriented languages, which require an entire class definition to be within a single class block (though sometimes a keyword other than Class is used to mark the start of the class definition). The main reason for allowing multiple class blocks is that they let you selectively include methods in a class as they are needed in a request, rather than having to compile all the methods in a class if any one is needed.
For example, Subroutine Paint
for Class Car
could be kept in a procedure
as follows:
PROCEDURE CLASS.CAR.PAINT !dupexit class car subroutine paint(%color is string len 32) ... end subroutine end class END PROCEDURE
!dupexit
causes the procedure to be exited on all but the first Include, which ensures that the subroutine is only defined once.
Attempting to define it more than once would result in compilation errors, of course.
Given the above procedure, code that needs to use Subroutine Paint
in class Car
could simply Include procedure CLASS.CAR.PAINT
:
include class.car.paint ... %chevy is object car ... %chevy:paint('Teal')
Subroutine Paint
can only be defined after it has been declared inside a declaration block for class Car
. If a class is broken up in such a way that some methods are in their own procedures, it is likely that the class's declaration blocks would all be contained in another procedure, called something like CLASS.CAR
in this example. It would be necessary to Include that procedure before the procedure that defines the Paint
method:
include class.car include class.car.paint ... %chevy is object car ... %chevy:paint('Teal')
Class block syntax
The full syntax of the Class block is:
[Local] Class className [Abstract] - [Alias=aliasList] - [Exception] - [Extends=extendList] - [Private] - [RequiredSuffix=suffix] [declaration blocks] [method definitions] End Class [className]
Syntax terms
Local | Indicates that the class can only be referenced inside the containing scope. A class declared inside a method or complex subroutine must be Local. A class in the outermost (Begin/End) scope can be either local, in which case it cannot be referenced in other scopes such as methods or complex subroutines, or not local, in which case it can be referenced anywhere in the request. |
---|---|
className | Consists of any valid alphanumeric and other non-separator characters. |
Abstract | Indicates that the class is abstract, that is, no instance of the class can be created unless it is an instance of a non-abstract extension class. |
Alias=aliasList | A list of aliases for the class, with each alias separated by the word And. See Aliases for class names. |
Exception | Indicates that the class is an exception class, that is an instance it can be thrown. |
Extends=extendList | A list of classes that this class extends. |
Private | Indicates that the class is a private class, meaning that only the class itself and friends can reference class members. Private is only available in Model 204 7.6 and later. See Friend declarations. |
RequiredSuffix=suffix | A non-quoted string of characters that must end the name of any object variable being declared for the class; provides a way of enforcing naming standards for class variable names. |
declaration blocks | Declarations for all variables and methods in a class. See Declaration blocks. |
method definitions | The definitions of the methods in the class. See Method definition syntax. |
The (one or more) class blocks for a class contain the declaration blocks and method definitions for the class, which together comprise the class definition. The class definition consists of zero or more declaration blocks and zero or more method definitions. It is valid to define a class with no declarations and no methods, though such a class is not going to be particularly useful.
Multiple class blocks for a class are, in effect, combined to form one class definition. Class blocks after the first may exactly repeat any of the Alias, Extends, or RequiredSuffix clauses from the first block for a class, but they may not change these clauses.
Classes must be declared outside of any other block structures such as For loops, If blocks, or other Class blocks.
Once a class is declared, you use the Object keyword on variable declarations to declare objects as instances of a class:
%malibu is object car %outback is object car
Colons in class names
The colon character (:) is allowed in SOUL class names. The following, for example, is a valid class declaration:
class customer:order
No colon-demarcated part of a class name can be the word System.
For example, System
, System:foo
, Foo:system
, and Foo:system:bar
are all invalid class names.
While there is no special meaning to the colon-demarcated parts of a class name, the intent is that the colons be used to group classes, and that higher-level qualifiers come before lower-level ones.
For example, in class name MiddleEarth:rohan:rider
, a Rider
class falls under a grouping of classes qualified by Rohan
.
This grouping, itself, falls under the larger grouping of MiddleEarth
.
Using the colon in this way is likely to make it easier to take advantage of future class management features that depend on a hierarchical, colon-based, class naming scheme.
Aliases for class names
As of Sirius Mods Version 7.8, you can define one or more alias names for an existing user class.
You do so by specifying an Alias list in the class declaration. For example,
you are changing a naming convention for some code, and you want to refer to user-defined class Blue
by another name, say Indigo
. Your class declaration would be:
class blue alias indigo
You can then use either the primary class name or the alias when declaring objects of the class:
%foo is object blue %bar is object indigo
An object of the alias class is compiled as an object of the primary class, and the runtime class of an object variable that was defined using an alias is the primary class. Consequently, all system messages you receive will specify the primary class.
For example, given the declarations above, if you call method TouchUp
for an object declared for the Indigo class:
%bar:touchUp
And method TouchUp
does not exist in the class, the error message you receive is:
MSIR.0733: Member TOUCHUP not found in class Blue
While this might seem potentially confusing, aliases are intended primarily for migrating class names, so any confusion will be limited to this migration period. In addition, only the owner of a class can declare an alias, so aliases are not likely to proliferate in your site's code.
Multiple aliases in the alias list are separated by the keyword And.
Declaration blocks
Declaration blocks are another feature of SOUL that is unusual among object-oriented languages. Declaration blocks describe all the variables and methods that make up a class, but they contain no code.
Most object-oriented languages allow variable declarations of all types to be mixed arbitrarily with method definitions of all types. Though this appears to be very convenient, it can make it difficult for users or maintainers of a class to determine the general framework of the class. SOUL requires all variables and methods of a class to be declared in a few blocks, and in those blocks, to be organized in ways that are significant to their behavior.
SOUL allows no more than one of each of four declaration blocks:
- Public
- Private
- Public Shared
- Private Shared
The Public block contains the declaration of all class members (variables that are associated with an instance of the class, and methods that operate on an instance of the class) that are "public", that is available to users of the class. The Private block contains the declaration of all members that are "private", that is available only to methods inside a class definition.
The Shared blocks contain members that are shared among all instances of the class and so are able to be referenced independent of any instance of the class. Public Shared members are available to all users of the class, and Private Shared members are available only within the class definition. Shared variables and methods are discussed in more detail in Shared class members.
The declaration blocks contain declarations for all variables and methods in a class, and they must appear before any method definitions.
Declaration block syntax
The format of the declarations blocks is:
{Public | Private} [Shared] [Allow clauses] [Disallow clauses] [Friend declarations] [member declarations] End {Public | Private} [Shared] [className]
Syntax terms
Public | Private | Indicates whether the block describes members that can be referenced outside the class (Public) or members that can only be referenced inside the class (Private) or by friend classes. |
---|---|
Allow clauses | Indicate special operations allowed by the class. They can only be specified in a Public (non-shared) block, and consist of the Allow keyword followed by Auto, Copy, DeepCopy, or Narrow. See Allow clause, below.
There can be multiple Allow clauses in a declaration block. Allow clauses are optional and can be interspersed with Disallow clauses, Friend declarations, and member declarations. |
Disallow clauses | Indicate operations not allowed by the class. They can only be specified in a Public (non-shared) block, and consist of the Disallow keyword followed by Discard, or New. See Disallow clause, below.
There can be multiple Disallow clauses in a declaration block. Disallow clauses are optional and can be interspersed with Allow clauses, Friend declarations, and member declarations. |
Friend declarations | Indicate friends of a class. Friends of a class are other classes that can access private members of the class or, in the case of a public friend, access public members of a private class. They can only be specified in a Private Shared block or the Public Shared block of a private class. In the latter case, a friend declared in a Public Shared block is called a public friend.
Friend declarations consist of the Friend keyword followed by the name of another class. There can be multiple Friend declarations in a declaration block. Friend clauses are optional and can be interspersed with Allow clauses, Disallow clauses, and member declarations. Note: Friend classes do not have to have been declared, nor do they ever have to be declared inside a request. Friend declarations are available in Model 204 7.6 and later. |
member declarations | Declare variables or methods in the class. These are described in Variable declarations and Method declarations and method types, below.
While member declarations are optional, they tend to appear in almost all declarations blocks; and a class without member declarations in some declaration blocks would be pretty much useless. Member declaration clauses can be interspersed with Allow clauses, Disallow clauses, and Friend declarations. |
End | Marks the end of the declaration block. It must be followed immediately by Public or Private which is then possibly followed by Shared. The keywords following End must exactly match the block type: for a Public block, End Public is required; for a Private Shared block, End Private Shared is required. The class name can, optionally, be indicated after the block type on the End clause. |
Allow clause
As the name suggests, the Allow clause indicates things one is allowed to do with the object instances of the class that one ordinarily would be allowed to do:
Allow Auto | Indicates that an Auto New clause may be used on the declaration of variables of the type of the class being defined. If the class is an extension class, Allow Auto must also be specified on the class's base class(es). |
---|---|
Allow Copy or Allow DeepCopy | Makes available the system Copy or DeepCopy method, respectively, for objects in the class. |
Allow Narrow | Permits narrowing assignments for objects in the class. |
Allow Auto and constructors
Prior to version 7.6 of the Sirius Mods, a class defined with Allow Auto is not allowed to contain a constructor. As of version 7.6, Allow Auto is allowed in a class that has a constructor. However, for an object variable declared with Auto New in such a class, an automatic instantiation of the object does not call any of the constructors in the class, even if the class has defined an explicit constructor with the name New.
For example, the following request prints %y:b = 0
— the class's New
constructor does not get called by %y:b
:
b class y public allow auto variable b is float constructor new end public constructor new %this:b = 1234 end constructor end class %y is object y auto new printText {~} = {%y:b} end
Disallow clause
As the name suggests, the Disallow clause indicates things that one is not allowed to do with the object instances of the class that ordinarily would be allowed:
Disallow Discard | Indicates that objects of the class cannot be explicitly discarded outside the class. Objects of the class can still be explicitly Discarded inside the class. For more about discarding objects, see "Discarding objects" |
---|---|
Disallow New | Indicates that explicit construction of instances of the class using the New constructor is not allowed outside the class. Objects of the class can still be explicitly created with New inside the class, and other public constructors for the class can be invoked outside the class. For more about object constructors, see "Creating object instances" |
Friend declarations
The Friend clause declares another class that can access private members in the class being declared or, if inside a Public Shared block (Private class only), another class that can access public members in the class being declared. The class declared as a friend need not have been declared, nor does it ever need to be declared inside a request. A class can only ever have friends in the same scope, so a Friend statement inside a Local Class declaration only applies to classes in the scope that contains the local class.
In the following example, class Moe
is a friend of class Curly
so can access private variables in class Curly
:
class curly public variable weight is float variable nyucks is float variable lastName is string len 32 end public private variable painlevel is float end private private shared friend moe end private shared end class class moe public variable salary is float variable oughtas is float variable lastName is string len 32 variable partner is object curly subroutine smackPartner end public subroutine smackPartner %this:partner:painLevel = @ + 5 end subroutine end class
If Moe
had not been declared as a friend of Curly
then subroutine smackPartner
in class Moe
would not have been able to reference the PainLevel
member of class Curly
.
Note that if we change the declaration of Curly
to be Private, Moe
could continue to access both public and private members of Curly
since it is declared inside the private Shared block. However, in that case, a class could be declared as a friend inside a Public Shared block, creating a public friend that can only access public members of Curly
. Public friends are only allowed for Private classes. In the following, Larry
is a public friend of Curly
so can reference public members:
class curly private public variable weight is float variable nyucks is float variable lastName is string len 32 end public private variable painlevel is float end private private shared friend moe end private shared public shared friend larry end public shared end class class larry public variable hairLength is float variable partner is object curly subroutine amusePartner end public subroutine amusePartner %this:partner:nyucks = @ + 2 end subroutine end class
Note that non-friends can always reference instances of private classes, they simply cannot reference class members. So the following is allowed:
class curly private public variable weight is float variable nyucks is float variable lastName is string len 32 end public private variable painlevel is float end private private shared friend moe end private shared public shared friend larry end public shared end class %curly is object curly %moe is object moe ... %curly = %moe:partner
Variable declarations
Variable declarations in a class declaration block must begin with Variable followed by the variable name (without a percent sign). This is followed by any standard SOUL variable declaration, with the exception that:
- Static is not valid in a non-Shared block.
- Global is not valid in a non-Shared block and requires the global name in a Shared block.
- Common is not allowed.
- The keywords to indicate "publicity" (Private, Private Shared, Public, and Public Shared) are allowed. If specified, they must match the publicity of the enclosing declaration block.
- Auto followed by New or an enumeration value is allowed.
- For ReadOnly and Protected variable declarations, like standard Model 204 variable declarations, the keyword Is immediately after the variable name on variable declarations is optional.
Variable declaration examples
An example follows of a class block with some variable declarations:
class car public variable price is fixed dp 2 variable make is string len 32 variable description is longstring end public end class
Variable declaration references
Class variables can be referenced with an instance of that class, that is,
with an object variable, like %gremlin
below:
%gremlin is object car ... printText The price is {%gremlin:price}
The colon (:) that separates the object variable %gremlin
and the class member variable price
, above, may alternatively be specified with a one or more blanks before and/or after it.
ReadOnly and Protected variables
It is possible to to declare a variable as ReadOnly or Protected. A ReadOnly variable can be examined by code outside the class, but it can only be updated inside the class. A Protected variable can be examined by code outside the class, but it can only be updated inside the class or by code inside an extension class. Since a ReadOnly or Protected variable must be accessible outside the class, it would make no sense for such a variable to be in a Private section, so this is not allowed.
ReadOnly and Protected variables are useful for providing an efficient means of accessing relatively static information about objects in a class. Unlike a property, retrieving ReadOnly or Protected variables requires no code to be run in the class.
The term Protected is something of a misnomer. In a very real sense, Protected variables aren't protected, at all. After all, they can be updated by extension classes. Their real purpose is to act as a sort of overridable variable, that is, a value that can be overridden by an extension class.
For example, suppose a class has a ReadOnly variable called PartNumber
. Perhaps PartNumber
is set by the class's constructor, and then it is never set again (for a particular object instance).
Now, suppose this class wants to allow extension classes to set a different PartNumber
, probably in their constructors.
One approach would be to make PartNumber
an Overridable ReadOnly property.
But this is a somewhat heavyweight approach, as it requires a bit of code (the property Get method) for each extension class, and this code has to be run every time PartNumber
is retrieved.
Instead, PartNumber
could be made a Protected variable, and an extension class's constructor could simply set it after calling the base class constructor.
This allows the extension classes to override the base class's PartNumber
using little extra code and using a very efficient access path for the value.
Protected variables differ from Overridable ReadOnly properties, however, in that an Overridable ReadOnly property always guarantees that the extension class's code is run, so it overrides the base class's properties. With a Protected variable, it would be possible for an extension class to set it, and for the base class to then set it to something else, undoing the extension class action.
Because they can be updated by both base and extension classes, Protected variables are probably most useful for very static values, like values that are only set by the constructors. Use of Protected variables that can be set throughout the life of objects of a class is likely to be error-prone, as it requires careful coordination of updates between the base and extension classes.
Method declarations and method types
In addition to variable declarations, a declaration block can also contain method declarations. There are four basic types of methods:
Function | A method that produces an output value. |
---|---|
Subroutine | A method that produces no output value. |
Property | A method that sometimes produces an output value and that can sometimes be set to a value, that is, be on the left side of an assignment statement. |
Constructor | A method that processes objects immediately after the objects are instantiated. A Constructor produces no output value. |
Method declaration syntax
The declarations for the four types of methods are very similar:
Function name - [(parameters)] [Is] datatype - [AllowNullObject] [CurrentRecord context] [Callable] - [Public | Private [Shared] ] Subroutine name - [(parameters)] - [AllowNullObject] [CurrentRecord context] - [Public | Private [Shared] ] Property name - [(parameters)] [Is] datatype - [ReadWrite | ReadOnly | WriteOnly] - [AllowNullObject] [CurrentRecord context] - [Public | Private [Shared] ] Constructor name - [(parameters)] - [Public | Private]
Syntax terms
name | The name for a Function, Subroutine, Property, or Constructor must follow the same rules as variable names and cannot be the same as another member of the class, whether a variable or method, public or private, shared or non-shared. |
---|---|
parameters | Method parameters have the same format as the parameter declarations of complex subroutines. For example:
class car public ... subroutine wash(%detergent is string len 16, - %howLong is float, - %howClean is fixed output) ... end public end class |
datatype | Functions and properties require a datatype, that is, an Is clause (though the keyword Is is optional).
|
AllowNullObject | The AllowNullObject keyword on a method declaration indicates that, even though the method operates on an instance of the class (an object), the method may be invoked with a null object variable. Ordinarily, a non-shared method referenced by a null object variable causes a (null-object reference) request cancellation error. Invoking a method even when the object variable is null may be useful in diagnostic code that displays object status or cleanup code.
Since a Constructor will always receive the just-created object as the object variable, AllowNullObject is not allowed on Constructor declarations. |
CurrentRecord | The CurrentRecord clause (CurrentRecord In File <name> , or CurrentRecord In Group <name> ) on a method declaration indicates that the method may only be invoked in a record context (established by a For Each Record loop, for example) for the declared file or group. Furthermore, CurrentRecord establishes a record context within the method, so method statements may reference a record field without having to be wrapped inside a record For loop (such as a For Record CurrentRecord loop). |
Callable | The Callable keyword, available only on Function declarations, indicates that the function can be Called, that is, invoked without a target for the result. Such a function may be Called even though it returns a value, and the Call may be explicit or implicit.
class car public ... function wash is float callable ... end public end class ... %beetle is object car ... %beetle:wash The last line can also be written: call %beetle:wash Callable is typically to be used on Functions that perform some action but also return a status or informational value. Since the caller might not care about the informational value, the Callable keyword allows the caller to invoke the Function without a target for the return value. Without the Callable keyword, an attempt to invoke a Function without a target for the result (the target of an assignment or input to another method or subroutine) causes a compilation error. Not having the Callable option makes sense for a Function whose chief purpose is to return a value, since invoking it without a return value defeats the purpose. For example, if you had: class cat public function weight is float ... end public end class It probably wouldn't make sense to do: %misha is object cat ... %misha:weight In this case, it is probably best not to put the Callable keyword on the Function declaration, and to catch likely typos or misunderstandings at compile-time. |
Public, Private, and Shared | The Public, Private, and Shared keywords are optional, but, if present, must match the declaration block type. That is, in a Private block, Private must be indicated; in a Public Shared block, Public Shared must be indicated; and so on. |
ReadWrite, ReadOnly, and WriteOnly | Properties behave very much like variables, with the exception that they have code that is run when their value is set or retrieved. This behavioral similarity between properties and variables can be formally described as an isomorphism between properties and variables. Practically, this means that a variable can be changed to a property, and vice versa, without worrying about how the member is used outside the class.
The keywords ReadWrite, ReadOnly, and WriteOnly indicate the type of access allowed for the property. The default, ReadWrite, indicates that the property can be both set and retrieved. ReadOnly properties can only be retrieved, and WriteOnly properties can only be set. ReadOnly properties are functionally equivalent to non-Callable Functions: one can think of a ReadOnly Property as a non-Callable Function, and vice versa, with no change to how the method can be used in applications. This makes the naming of such a method a ReadOnly Property or non-Callable Function primarily a hint to the users or maintainers of a class — a ReadOnly Property, as the name suggests, is a static attribute of an object, while a non-Callable Function is thought of as performing some dynamic operation. Any method that is to be Callable must be a Function rather than a Property. |
Constructor | Constructors behave like subroutines but cannot be called directly. Instead, they are called by SOUL when an object is first created. |
Declaration block example
In the following example, a class block consists of one public block which contains one variable declaration, one method (subroutine) declaration, and a disallow clause:
class stooge public variable funniness is float subroutine slap(%howHard is float) disallow new end public end class
The Auto keyword on object declarations
While it is not a daunting task to create an instance of an object, sometimes it can be unclear exactly where to instantiate an object. Furthermore, certain objects just do not make sense as nulls, so instantiating them is simply a chore. This chore can be simplified by placing the object instantiation statement immediately after the declaration:
%stringArray is object stringList %stringArray = new
But this mixes code with variable declarations, which might be deemed ugly, at best, and doesn't work, anyway, if the object is actually inside a class — code can't be placed in side a Public, Private, Public Shared, or Private Shared block.
To get around these issues, you can use the Auto keyword for object variable declarations. For example:
%stringArray is object stringList auto new
An Auto keyword may be followed by either:
- An enumeration value, for enumeration variables.
- An instantiation method, which currently can only be New (with no parameters), for object
and collection variables (for classes for which New is valid). Any instantiation that requires parameters must be done programmatically rather than on the variable declaration (for more information, see "Using New or other Constructors".).
The automatic instantiation of an object for a variable whose declaration
includes Auto New does not use any constructor that may be defined for
the object class, even a constructor named New
.
The instantiation is generic, so to speak.
Auto New is valid for user-defined class object variables only if the class definition includes Allow Auto.
The Auto keyword can be very useful for array-like objects, especially collections. For example, it might seem natural to be able to start adding immediately to a stringlist without actually instantiating it:
%colors is object stringlist auto new %colors:add('Turquoise') %colors:add('Amber') ...
Collections seem particularly suited for being immediately added to:
%itemPrice is collection namedArraylist of float auto new %itemPrice('Widget') = 22.34 %itemPrice('Thingy') = 9.84 %itemPrice('Whatsit') = 64.18 ...
The Auto keyword can also be quite useful for enumerations. An Auto keyword on an enumeration variable acts very much like an Initial clause on a String, Float, or Fixed variable:
%haveWarned is enumeration boolean auto false
While it might be tempting to think about the Auto clause as doing the instantiation or assignment — as if an assignment immediately followed the declaration — this is not quite the way Auto works. Auto waits until the first reference to the variable, as long as it is not an assignment to the variable, before assigning to it. That is, the following declarations do not cause three Stringlist instances to be created:
%red is object stringList %white is object stringList %blue is object stringList
But if somewhat further down, the following statement is executed, a Stringlist instance is automatically created before the Add method is invoked:
%blue:add('Azure')
If the first reference to an Auto object is an assignment:
%red = %blue
the Auto attribute will never be exercised, even if the assignment source is Null, and even if the object is subsequently discarded.
Note: The effect of Auto is restricted to the first reference to a variable in a request.
Member reference
Defining a class entails defining its members, that is, variables and methods. In order to access one of those variables or invoke one of those methods, an expression must identify the member, and:
- An object instance, for a non-shared, non-constructor member
- The member's class, for a constructor or shared member (as described below, in some circumstances the class is implicit in the expression)
Here are three typical examples of member reference:
- an object instance, for a non-shared, non-constructor member
- the member's class, for a constructor or shared member (as described below, in some circumstances the class is implicit in the expression)
Here are three typical examples of member reference:
print %doc:value
The Value function is invoked using the object
%doc
.%slis = New
A new instance object of the Stringlist class is assigned to the object variable
%slis
. Note that here the class identification is implicit.%num = %(daemon):parentNumber
The shared ParentNumber property is invoked to return the user number of the parent of the current thread.
%(daemon)
identifies the class of the shared property.
All of the above cases use the following syntax:
[identifyingPhrase:]member
The colon (:) preceding the member may optionally be preceded or followed by any number of blanks; here are some examples:
%str = %doc:Value('/order/cust/@name') %name = %custInfo : name %addr = %custInfo: - address %phone = %custInfo - :phone
The 'identifyingPhrase:' phrase is optional when invoking a Constructor or virtual constructor, in some circumstances, as described below.
- If 'identifyingPhrase:' is present, the member phrase may either be a member name or a method variable.
- If 'identifyingPhrase:' is not present, the member phrase must be a member name.
For class members that are neither shared nor constructors, the member name (or method variable)
must be preceded by an expression that
denotes an object of the class, and a colon.
The immediately preceeding examples are all of non-shared/non-constructor method invocation (...:Value...
) or
class variable access (%custInfo:...
).
For method invocation, the object instance (above, %doc
) is method object
input to the method.
For variable access, the class variable (name
, address
, phone
) for the object instance (%custInfo
) is accessed.
Since neither a constructor nor a shared member operates on an object instance, referencing a constructor or a shared member requires identifying only the class to which the member belongs. For constructors and shared members, either of the following two syntax forms can always be used:
- Explicitly citing the class name:
%(className):member
For example:
%tok = %(system):arguments:stringTokenizer
- Implicitly identifying the class name:
object:member
For example:
%daem object daemon if %daem:amDaemon then ...
In this form of identifying the class, note that since no specific object instance is operated upon, the value of the method object (
%daem
) may be Null.
In addition to the above two syntax forms for identifying the class, in some circumstances the class can be implicitly identified for a Constructor or virtual constructor (a shared Function, Property, or Variable whose type is an object in the class):
- The first, most common, set
of circumstances is when the constructor is on the right hand side of an assignment statement:
%var = constructor
Note that
%var
must be an object variable, andconstructor
must be a Constructor or virtual constructor in the class of%var
.For example, using the New Constructor:
%strLis object stringlist %strLis = new
A shared Function example is:
%doc object xmlDoc ... for each record ... %doc = newFromRecord
- The class identification may also be omitted before a constructor if
the constuctor's result is an argument (but not the method object) of another method.
For example:
%origLis:copyItems(list('a', 'b', 'c'))
In this example, the result of the virtual constructor List is the Stringlist argument of the CopyItems method.
- A final, albeit rare,
set of circumstances is when the result of the constructor is the right-hand comparand, for example in an If
statement:
If object eqOrNe constructor Then
Note:
object
must be an object expression, andconstructor
must be a Constructor or virtual constructor in the class ofobject
.- The comparison operator (
eqOrNe
) must be one of Eq, =, or Ne. object
may not itself be a constructor without a preceding identifyingPhrase and colon.- Only the second operand in a comparison may use implicit class specification, that is, you may not do:
If constructor eqOrNe object Then
Base class members
If an object expression is for an extension class, and you want to refer to a non-inherited member of a base class, you must specify the parenthesized base class name before the member name (or method variable). For example:
class ropeList extends stringList ... end class %rope object ropeList %rope = new %rope:(stringlist)add('sisal')
Intrinsic methods
As with non-intrinsic methods, you use the following syntax to invoke an intrinsic method:
identifyingPhrase:method
For intrinsic methods, however, the identifyingPhrase and colon (:) are always required; there is no circumstance that allows implicit class identification.
An example of invoking an intrinsic method is:
%sqrt = 169:SquareRoot
In addition, there are certain unusual syntax rules for invoking intrinsic methods, as described in "Intrinsic method syntax: special cases".