Local and Common entities
Overview
Many methods are useful in a wide variety of applications. System methods usually fall into this category, as they are intended to provide basic services that are useful in many applications. Methods that are widely useful will usually be contained in a class, often as Public or Private methods, but sometimes as enhancement methods, and sometimes as non-enhancement shared methods.
On the other hand, some methods have only relatively local applicability. That is, while useful in a specific application context, they are not likely to be useful in other contexts. The following are cases where using such essentially-local methods might be beneficial:
- As a substitute for a large block of code in the main line of application processing, increasing code readability.
- As a container for application-specific code that is repeated exactly or almost exactly several times in a block of code, yet does not lend itself to a loop construct.
- As an alternative way to implement a locally recursive process.
There are many other situations where locally applicable methods would be useful.
SOUL local methods facilitate the writing of locally applicable methods. Local methods provide all the functionality of simple subroutines, and more:
- They can be nested inside other methods.
- They can have local variables.
- They can have access to containing context variables or not as one wishes.
- They have all the capabilities of method calls including optional and named parameters and the ability to return values or even be settable (a property).
- They are invoked with object-oriented syntax.
Accompanying local methods are additional entities that have local scope:
- Classes, enumerations, and structures have local counterparts.
- Local aliases are a simplified way to call a non-local method in a local context.
- Local variables are variables that can be used only within a local method but that have greater persistence than simple percent variables.
Common methods and aliases are variations of local methods and aliases that are designed to be similar enough to standard SOUL Common variables and complex subroutines to replace them in existing applications.
Scope and locality
One important thing to understand about local methods is the concept of scope. A scope is a block of code whose variables are isolated from other scopes. For example, the outermost scope in SOUL: the block of code inside the Begin and End block for a request, but not inside a method or complex subroutine, is one scope.
Each complex subroutine is another scope, and each method, yet another.
This means that a particular variable name, say, %foo
, can only
be declared once in a given scope, and that variable is distinct from an
identically named variable in another scope.
So, in the following example,
%foo
is a Stringlist object variable in the outermost scope, a Unicode variable
in complex subroutine WorkHarder
, and a Float variable in function Cipher
in class
Neat
.
begin %foo is object stringlist ... subroutine workHarder %foo is unicode ... end subroutine ... class neat ... function cipher is float %foo is float ... end function ... end class ... end
Because the outermost, complex subroutine, and method scopes are isolated, there is
no danger of confusion for the %foo
variables in the preceding example.
In fact, there is no way to access the outermost scope %foo
Stringlist from the inside
of complex subroutine WorkHarder
or function Cipher
, even if no local %foo
had been
defined in these scopes.
Scope applies to what are termed local variables, that is, variables
that are local to the scope.
Class variables are always accessed via an object variable of the class,
so they are not affected by local scoping.
That is, a variable in class Clever called Junk can only be accessed via an object
variable of class Clever.
So, if %smart is an object of class Clever, variable Junk could be accessed via
%smart:junk
.
Sometimes, the object variable is implicit.
For example, inside an instance (non-shared) method inside of class Clever, a
%junk would be interpreted as %this:junk
if no local variable called %junk were
defined.
Similarly, if function Piggledy on local object variable %higgledy produced a
Clever object, the Junk variable in that object could be accessed via
%higgledy:piggledy:junk
.
Again, the Clever class variable is implicit here.
Shared variables in a class are global in scope, that is, they maintain their
value and can be accessed from inside of any scope.
This can be done using the (<classname>):<variable>
syntax or
the %<object>:<variable>
syntax.
Because, the class distinguishes the variable from other identically named variables
in other scopes, there is no need to scope such variables: they can be
accessed from any scope inside a SOUL request.
Of course, private variables in a class can only be accessed from methods inside the class. So, in some sense, a class is, itself, a scope where private variables in the class might be accessed. However, for the purposes of most discussion, scope will refer only to the scope for local variables, so it will only refer to the outermost (Begin/End) scope, complex subroutine scope, and method scope.
It is worth mentioning common variables with regard to scoping. Common variables, as their name might suggest, can be accessed from many different scopes. However, they must be declared as common in each scope in which they are to be accessed.
For example, to change the previous example slightly:
begin %foo is object stringlist common ... subroutine workHarder %foo is unicode ... end subroutine ... class neat ... function cipher is float %foo is object stringlist common ... end function ... end class ... end
In this example, the %foo
variables in the outermost scope and in the scope
for method Cipher
in class Neat
reference the same Stringlist object variable.
On the other hand, %foo
in the complex subroutine WorkHarder
is a local variable,
so the %foo
common variable cannot be accessed in that scope.
Defining and invoking a local method
The local method facility lets you create methods that are not part of a class and are only available inside the scope within which their definition is contained. A local method does not require a separate declaration block before it is defined. Typically, a local method's declaration begins the method definition.
Local methods are declared with the keyword Local, followed by the method type, followed by the method name and description:
Local method declaration syntax
Local methodType methodName methodDesc
Where:
methodType | The method type, which has the following syntax:
Subroutine | Function | Property The types of local method are like those of class methods:
| ||||||
---|---|---|---|---|---|---|---|
methodName | The method name, which can be any name that follows the rules for SOUL %variables, possibly preceded by the class that the method enhances (locally). However, like variable declarations inside of Class blocks, the name does not start with the percent (%) sign.
In addition, the name cannot be the same as a simple percent variable in the same scope (though it can be the same as a class variable or method, as discussed in "Using Defer and Expose"). The method name syntax is: [(className):] methodname If a class name is not specified, the method is assumed to be a shared, non-enhancement local method. | ||||||
methodDesc | The method description, which has exactly the same structure as the method descriptions for class variables. That is, a local method has an optional parameter list which can contain required and optional, named and unnamed parameters, and their types. The parameter list is followed by a method result type (for Functions and Properties) and then possibly by further qualifiers.
The method description syntax is: [(parameters)] [is datatype] [qualifiers] While most qualifiers can be used for both local and class methods, some qualifiers (such as Public) make no sense in local method declarations, so they are not allowed, while a few others, like Expose (discussed later), only make sense for Local and Common methods. |
An example of a local function declaration follows:
local function (float):addAndMult(%what is float) is float
Unlike class methods, local methods can and must be declared inside some other scope. And unlike complex subroutines, local methods can be contained inside any scope, not just the outermost scope.
Like simple variables, local methods, themselves, have scope. That is, they can only be called inside the scope in which they are defined. This is different from complex subroutines, which must be declared in the outermost scope, but can be called from any scope.
In general, as in the following example, a local method declaration is followed by the definition of the method (although as described in "Using Defer and Expose", you can declare a method and then define it later in the request if need be).
local function (stringlist):bytes is float %i is float %bytes is float for %i from 1 to %this:count %bytes = %bytes + %this:itemLength(%i) end for return %bytes end function
When you invoke a local method (that operates on a specific object instance) you do not qualify the method name with a classname. The syntax for the invocation is simply:
object:method
A different syntax is warrented for methods that do not operate on a specific object instance, as discussed in "Using non-enhancement and recursive local methods".
The following request is a simple example that features a local method. The local function in this program calculates the number of bytes in all the items in a Stringlist:
b local function (stringlist):bytes is float %i is float %bytes is float for %i from 1 to %this:count %bytes = %bytes + %this:itemLength(%i) end for return %bytes end function %sl is object stringlist %sl = new text to %sl The noise Of worldly fame is but a blast of wind, That blows from diverse points, and shifts its name, Shifting the point it blows from. end text printtext {~} = {%sl:bytes} end
Using Defer and Expose
As described in the previous section, a local method does not require a declaration separate from its definition. However, you can do so in a situation where it is warranted to declare a method before defining it (because method A calls method B which calls method A). In such a case, you specify a separate method declaration, append the keyword Defer to it, then repeat the declaration later in the program as part of the method definition:
local function (stringlist):bytes is float defer ... (other code) ... local function (stringlist):bytes is float %i is float %bytes is float for %i from 1 to %this:count %bytes = %bytes + %this:itemLength(%i) end for return %bytes end function
A local method has its own scope and its variables are not visible to the containing
code.
Similarly, variables in the containing code are not visible to the local method
— unless the keyword Expose is specified on the method declaration.
In the following example, the Multiply
local method references the %multiplier
variable in the containing code:
b %multiplier is float local function (float):multiply is float expose return %multiplier * %this end function %multiplier = 1.5 printtext {~} = {17:multiply} end
When you specify Expose
, the %variables (and local methods)
that are exposed are
those in the containing context that are declared before the
method definition.
Labels, images, and screens are not exposed.
A percent variable inside an
exposed method "hides" a same-named variable in the exposed context.
In the following request,
the %multiplier
inside the local Multiply
method is
the one that is used inside the method — not the %multiplier
declared
in the exposed context and assigned later:
b %multiplier is float local function (float):multiply is float expose %multiplier is float initial(1.2) return %multiplier * %this end function %multiplier = 1.5 printtext {~} = {17:multiply} end
You are allowed to reference a hidden variable before it is hidden, as in this example:
b %multiplier is float local function (float):multiply is float expose printText {~} = {%multiplier} %multiplier is float initial(1.2) printText {~} = {%multiplier} return %multiplier * %this end function %multiplier = 1.5 printtext {~} = {17:multiply} end
The first PrintText
statement in the Multiply
method prints the %multiplier
.
in the exposed context.
The second PrintText
statement prints the %multiplier
declared in the Multiply
method.
While the value of this tactic in a small local method like the one above may be questionable, it might be useful in a larger local method. You might want to access a containing context's data after writing a great deal of code that uses the same variable name internally for something else.
Local methods that expose the containing context are nestable. Consider the following fragment:
b ... local subroutine (stringlist):a expose ... local subroutine (stringlist):b ... local subroutine (stringlist):c expose ... local subroutine (stringlist):d expose
In this example, method A
can access the outermost scope's variables.
Neither methods B
, C
, nor D
can access the outermost scope's variables,
nor can they access method A
's because method B
is not exposed.
Methods C
and D
can both access method B
's variables, and method D
can access method C
's.
Probably this level of nesting will be quite rare.
See also
Using non-enhancement and recursive local methods
In all the examples in the preceding subsection, the local methods are enhancement methods, which is likely to be the most common type of local method. Local methods can enhance intrinsic, system, or user-defined classes. You can also define a non-enhancement local method.
If you define a non-enhancement local method, you can invoke it in either of two ways:
- With
%(local):
, the syntax for invoking a shared method with the term Local in parentheses instead of a class name. - With simply a percent sign (%) preceding the method name.
Here is an example:
b %a is float %b is longstring %c is fixed dp 2 local subroutine debug expose printText {~} = {%a} printText {~} = '{%b}' printText {~} = {%c} end subroutine %(local):debug %a = 12 %b = 'north' %c = .99 call %debug end
The result is:
%a = 0 %b = %c = 0.00 %a = 12 %b = 'north' %c = 0.99
Note:
- For a local subroutine as in the example above, the keyword Call
is also legal before
%(local):
, but it is required if using just the%
. - The name Local is not allowed as a class name.
- As stated earlier, the name of a local method cannot be the same as a percent variable in the same scope.
As a special case to support recursive local methods, unexposed methods are allowed to call themselves. The following definition is valid:
local function (float):factorial is float if %this le 1 then return %this end if return %this * (%this - 1):factorial end function
Using locally inherited methods
Like enhancement methods, local methods never automatically apply
to any extension classes.
For example, if the declaration of local function bytes
is:
local function (stringlist):bytes is float
Simply applying bytes
to object %foolist
(%foolist:bytes
)
is not allowed if
object %foolist
is of class MyStringlist
which extends Stringlist.
But the following is valid:
%foolist:(stringlist)bytes
You can use %foolist:bytes
,
however, if you first specify an explicit Local Inherit statement, as described below.
Local Inherit statement syntax
Local Inherit (extensionClass)::method From baseclass [baseMethod]
Where:
extensionClass | The name of the extended class for which you want the local method to be inherited. |
---|---|
method | The name of the local enhancement method that you want to be inherited for extensionClass. |
baseClass | The class that extensionClass extends. |
baseMethod | The base method name, which is assumed to be the same as method, the inherited name. |
In the case of the preceding example, the appropriate Local Inherit statement is:
local inherit (myStringlist):bytes from stringlist
Then the local (stringlist):bytes
method is invoked by:
%foolist:bytes
If you want to use a different name, say characters
,
for the inherited method (bytes
, above), you specify:
local inherit (myStringlist):characters from stringlist bytes
And to invoke the method, you specify:
%foolist:characters
Note: You can only locally inherit local methods.
These method-specific variables can be used only within a local method. They have local scope: they can only be accessed inside the local method in which they are declared, and they have no meaning outside that method.
A Persistent local variable is set to its initial value every time the method that contains the local method is invoked. This unusual characteristic is elaborated further, below.
A Shared local variable is never reset to its initial value; it maintains its value over the entire request (everything between Begin and End) regardless of where it is contained.
A simple %variable in a local method is initialized (and stacked as needed) every time the local method is entered. A Persistent local variable, however, is initialized and stacked during entry to the context that contains the local method that contains the variable. This context may be a method (local or not), a non-OO SOUL complex subroutine, or a Begin/End block.
For example,
when the Sleep
method below is called, %timesSnored
is initialized to 0:
class person ... subroutine sleep ... local function snore is float %timesSnored is float persistent %consecutive is float ... %timesSnored = %timesSnored + 1 return %timesSnored end function ...
If Snore
is called:
%timesSnored
is not initialized.- The
Sleep
method is not initialized (that would make it useless for counting snores). - Simple local variable
%consecutive
is initialized.
If Sleep
is called recursively, however, the current value of %timesSnored
is stacked, as are all simple variables within Sleep
's context.
They are then restored upon the return from the recursive call.
If you do want a local variable to persist over recursion, you declare it a Shared variable, and like a Common %variable, it will never be reinitialized or stacked:
local function snore is float %timesSnored is float shared ... %timesSnored = %timesSnored + 1 return %timesSnored end function
Note: If the code that contains the local method that contains a Persistent variable is the Begin/End block or a complex subroutine, no variable value re-initialization occurs — in this case, there is no difference between a Persistent local variable and a Shared local variable.
The syntax for declaring a Persistent or Shared local variable is:
%name Is type [Persistent | Shared]
Where:
name | The name of the local variable. |
---|---|
type | The variable's datatype definition. |
Notes:
- The Persistent and Shared keywords must be the last keywords on the %variable declaration.
- Persistent and Shared local variables may be declared inside a Local Property block but outside the Get/Set blocks, as shown in the following example:
local property concat is longstring %shadow is longstring persistent set %shadow = %shadow with %concat end set get return %shadow end get end property
Prior to the introduction of Persistent variables, only the Get/Set blocks were allowed inside a Property block.
Working with Persistent variables
Persistent variables provide a way for a method to locally maintain some value that needs to be remembered from call to call. The following examples show how this capability can be used to make local variables and properties interchangeable.
For example, suppose one subroutine locally manipulates %balance
,
and a local function BalanceChange
returns the change since the last time
you checked:
subroutine calculate ... %balance is float ... local function balanceChange is float expose %lastBalance is float persistent %return is float %return = %balance - %lastBalance %lastBalance = %balance return %return end function ...
Persistent variable %lastBalance
in balanceChange
gets initialized to 0 whenever
Calculate
is entered, at the same time that %balance
gets initialized.
Now, suppose you want to change %balance
, as above,
into a Property.
You can use Persistent variable shadow
to maintain the value of %balance
:
subroutine calculate ... local property balance is float %shadow is float persistent get return %shadow end get set %shadow = %balance end set end property ...
Once you have the local property definition above, then inside subroutine Calculate
,
references to the Balance
property can simply use a percent sign instead of
using a %(local)
qualifier (introduced in "Using non-enhancement and recursive local methods").
For example:
%balance = %balance + %adjustment
Consequently, you can change a local variable to a property and vice versa without affecting any of the code that uses the property.
Carrying this reversibilty concept further, you could
also readily change a Common variable to a Property.
For example, to turn Common variable %foo
into a property, you might do something
like the following:
class common public shared property foo is longstring ... end public shared ... end class
Where you want to access the property locally as %foo
, you specify
a local alias:
local alias foo for (common):foo
Then when you type %foo
in that scope, you access the
property (common):foo
.
Using Persistent versus exposing the local method
A Persistent variable maintains its value across subsequent invocations of the local method that contains it, but only until the next time the container of that method is called. A Shared variable maintains its value for the duration of the request.
Consider the method called MonthlyPayment
:
class labor public ... function monthlyPayment is float ... end public ... end class
Suppose inside MonthlyPayment
you want a local property to keep track
of the total payments, but which will never return a value larger than 2000.
The property might look like:
class labor ... function monthlyPayment is float ... local property amount is float %currentAmount is float persistent get if %currentAmount gt 2000 then return 2000 end if return %currentAmount end get set %currentAmount = %amount end set end property ... end function ... end class
Because %currentAmount
is Persistent, it will get set to 0 every time MonthlyPayment
is entered.
If it was Shared, its value would be preserved between invocations of
MonthlyPayment
, which is not what is wanted here.
And if it was
a simple local variable inside Amount
, it would
get set to 0 every time Amount
was invoked.
Compare the solution above to the following alternative.
You can accomplish the same end if you make %currentAmount
a simple variable inside of MonthlyPayment
, and if you make Amount
an "exposed" method (introduced in "Using Defer and Expose"):
class labor ... function monthlyPayment is float ... %currentAmount is float ... local property amount is float exposed get if %currentAmount gt 2000 then return 2000 end if return %currentAmount end get set %currentAmount = %amount end set end property ... end function ... end class
But this approach has weaknesses:
%currentAmount
can be set or retrieved throughoutMonthlyPayment
, which may be inviting a problem.- Making
Amount
exposed opens the door to problems or bugs caused by variables shared betweenAmount
andMonthlyPayment
.
The latter of these is not necessarily a weakness —
you might prefer that
Amount
is able to see MonthlyPayment
's variables, or you might
want %currentAmount
to be manipulated in code outside of Amount
.
But Persistent can occasionally be a superior way
to provide just the right level of isolation of a local variable.
Local classes, enumerations, and structures
It may be advantageous in rare cases to define an entire class for use for a local scope. SOUL support for local tools includes local classes, as well as for local enumerations, and local structures.
A local class is a class whose members and objects are available only inside the scope within which their definition is contained. For example, within the definition of a method in a class, you might want to make use of a local class:
class myclass ... public function foobar is float end public ... function foobar is float ... local class mumble public subroutine spec5(%newval is float) end public subroutine spec5(%newval is float) ... end subroutine end class mumble ... call spec5(19) ... end function ... end class myclass
A local class block is no different from a normal class block (as described in "Classes and Objects") except that the class name must be preceded by the keywords Local Class.
You may define a local class in any scope in a program, including within a local method and within the outermost scope (directly within Begin and End statements). A local class in the outer scope is different from a normal class in that it can only be referenced by outer scope code, simple subroutines, and local "exposed" subroutines.
A local class "hides" any same-named class in the global scope. That is, within the local scope, local class member and variable names take precedence, and out-of-scope same-named methods and variables cannot be accessed. Similar hiding was discussed for variables in local methods (see Using Defer and Expose), but unlike local methods, which have a workaround for this hiding, there is no way to unhide a hidden class.
Local structures and enumerations
Just as you can prepend the keyword Local to a class block, define the class, then apply the class contents in the local scope, you can also define a locally-scoped enumeration or a locally-scoped structure.
Within a method or complex subroutine, only such a local enumeration or structure is allowed: the keyword Local is required for any enumeration or structure block you declare inside a method or complex subroutine.
The syntax of an enumeration block is described in User Language enumerations. The syntax of a structure block is described in Structures.
Local aliases
In certain circumstances, you can improve code readability and possibly coding efficiency by mapping an alternative name to an existing method for a local scope. You do this by declaring a local alias.
For example, consider a case where you have a class named Utility
that
contains an enhancement method named Shift
that operates on strings:
class utility public shared function (string):shift(%number is float) is longstring ... end public shared end class
Suppose that in some context in your program, you use many statements like these to operate on objects that belong to another class:
%a = %b:(+utility)shift(1) %c = %d:(+utility)shift(2) %e = %f:(+utility)shift(3)
As a way to eliminate the repetition of the qualifying classname, you can define an alias:
local alias (string):shift for (+utility)shift
Then the statements above that are in the same local scope as the alias statement can be shortened to:
%a = %b:shift(1) %c = %d:shift(2) %e = %f:shift(3)
An alternative way to accomplish the same economy with a little more effort is to define a local function:
local function (string):shift(%number is float) is longstring return %this:(+utility)shift(%number) end function
Although you can declare and use a local alias for system and user methods, they are probably most suited for enhancement methods. Because of the potential confusion caused by aliases, you probably should use them sparingly.
Local alias declaration syntax
Formally, the syntax for an alias declaration is:
Local Alias [(class):]alias For - [(+enhancingClass) | (containingClass):]method
Where:
class | The class of the objects on which the actual method, method, is defined to operate.
If the alias is for an instance method, you must specify the class before the alias name. If for a shared, non-enhancement method, you do not specify a class before the alias name. |
---|---|
alias | The name of the method alias. This name typically is the same as method, but it does not have to be. |
+enhancingClass | The name of the class in which method is a member. Specify this class if method is an enhancement method. |
containingClass | The name of the class in which method is a member. Specify this class if method is a shared method. |
method | The name of the actual method for which the alias is to be used. |
Examples
The following series of examples demonstrates how to use local aliases in a variety of contexts.
- The following fragment shows an alias for an instance method
(Stringlist Count):
local alias (stringlist):number for count ... %sl is object stringlist ... print %sl:number
- In the following example, local alias
Foolish
is used for public subroutineSilly
in classAbsurd
:class absurd ... public ... subroutine silly ... end public ... end class ... local alias (absurd):foolish for silly ... %whacky is object silly ... %whacky:foolish
- In the following example, a local alias is used for
an enhancement method in class
Util
for Stringlists. This is likely an atypical case where the local alias name is not the same as the enhancement method name.class util ... public shared ... function (stringlist):hash is longstring ... end public shred ... end class ... local alias (stringlist):digest for (+util)hash ... %sl is object stringlist ... print %sl:digest
- As shown in this example for the System class method Arguments,
a local alias statement for a non-enhancement, shared method must not
have a class name preceding the alias name:
local alias args for (system):arguments ... printtext Input arguments: {%args}
Note that a non-instance alias is simply accessed by the alias name preceded by a percent sign (
%args
) — just like a local non-instance method (see "Using non-enhancement and recursive local methods", for example). The alternative access to this is to use the keywordLocal
, as in:printtext Input arguments: {%(local):args}
You can also use the simple percent-sign access for an alias for a non-instance user-defined method, as in the following:
class foobar ... public shared ... property mostFoo is float ... end public shared ... end class ... local alias maxFoo for (foobar):mostFoo ... %maxFoo = 77 ... print %maxFoo
- You can also use a local alias to provide an alias for a hidden method.
For example, suppose your program has many Stringlist Add calls in a local context,
and you are required to make the same modification to each of those Add calls.
Rather than changing each Add call "by hand," a convenient solution is to
create a local Add method that applies the modification:
local function (stringlist):add(%what is longstring) - is float ... end function
To call the system Stringlist Add method from within the local Add function, you can use an alias like the following — otherwise an Add call inside the function is a recursive call to itself:
local function (stringlist):add(%what is longstring) - is float local alias (stringlist):realAdd for add ... return %this:realAdd(%what) end function
Common methods and aliases
You can define Common methods and Common aliases. The only methods you can declare as Common are properties and subroutines. The only Common alias you can declare is for a Shared Property.
Common properties and Common subroutines are designed to ease the migration of existing SOUL code elements to comparable SOUL object-oriented constructs. You can define a Common property, for example, that is largely interchangeable with a non-OO SOUL Common variable. Similarly, a Common subroutine can be used interchangeably with a non-OO SOUL complex subroutine. Common functions do not exist, because prior to SOUL there were no user-created User Language functions (so no functions to migrate).
Declaring and invoking a Common method
Like a Local method, a Common method is not part of a class and its contents are not visible to the containing code. A Common Property is only available inside the scope within which its definition is contained, but a Common Subroutine (which must be at the outermost scope) is available throughout the request.
Its declaration syntax is like that for a Local method (Defining and invoking a local method).
Common method declaration syntax
A Common method is declared with the keyword Common, followed by the method type, followed by the method name and description:
Common methodType methodName methodDesc
Where:
methodType | The method type, which has the following syntax:
Subroutine | Property |
---|---|
methodName | The method name, which can be any name that follows the rules for SOUL %variables, but it cannot be the same as a simple percent variable in the same scope. |
methodDesc | The method description, which has the same structure as the method descriptions for Local methods:
[(parameters)] [is datatype] [qualifiers] An Initial clause is not allowed. The Expose qualifier is allowed only for Common subroutines. The Defer qualifier is allowed for both Common properties and subroutines. |
An example of a Common property declaration follows:
common property foo is float
A Common Property may be contained inside any scope in a request; a Common Subroutine must be contained in the outermost scope. A Common Property must be called inside the scope in which it is defined. A Common Subroutine may be called from any scope.
The way you invoke a Common method depends on whether the method is a Property or a Subroutine, as described in Using a Common Property and Using a Common Subroutine.
Using a Common Property
The principle reason for the existence of Common properties and aliases is to enable a Common variable declaration to be converted into a Common Property declaration without changing the code. You can only declare a Common property that has no parameters, but unlike common subroutines, you can declare a Common property within any scope in a request.
As an example, consider the following Common property definition:
common property notSmall is float %shadow is float shared set if %notSmall gt 10 then %shadow = %notSmall else %shadow = 10 end if end set get return %shadow end get end property
To access this property, since it is not a member of a class, you must declare as Common within a scope a variable with the same name as the property name:
%notSmall is common
Declaring a same-named Common variable is necessary even in the scope in which the Common property is defined. If you specify datatype attributes on the Common variable declaration, they must match the property datatype:
%notSmall is float common
If you are converting Common variables to properties, the Common Property definition block may well be in one Included procedure while the common variable declaration is in another. Or you might want a Common variable that is declared in some standard Include block to be a Property in a particular request. You might just have the Common Property block at the top of the request (after the Begin) and let the Common variable be used wherever it is already declared.
For an additional example featuring Common properties and variables, see Using Common aliases.
Using a Common Subroutine
Just as Common properties enable a fairly straightforward conversion of standard SOUL Common variables to properties, so Common subroutines simplify the conversion of SOUL non-OO complex subroutines to SOUL OO subroutines. Such a subroutine conversion is advantageous because, without having to change the invocation of a formerly complex, now Common, subroutine, you gain the benefits of object-oriented methods:
- Automatic initialization of local variables.
- Optional and named parameters.
- Better recursion behavior.
Note: Unlike complex subroutines, common subroutines must be defined before they are called (or they must have been declared earlier using the Defer keyword).
Since Local subroutines are tightly scoped and restricted to the scope in which they are defined, they are not suited to the conversion task because complex subroutines are available throughout the request, that is, essentially common.
A Common Subroutine is like a Local Subroutine except:
- It is invoked like a complex subroutine:
Call subroutineName
Note that no percent sign (%) precedes the subroutine name.
- It may be contained only in the outermost (Begin/End) scope of a request; it cannot be nested.
- It is in the same namespace as complex subroutines, so Common and complex subroutines may not have the same name.
Invoking a Local method that has the same name as a Common subroutine does not preempt (hide) the invocation of the subroutine, because the Common subroutine must be preceded by the Call keyword. This is true even if the Local method is a subroutine.
For example, Common subroutine Jiggle
is
followed by a Local subroutine with the same name.
The Call Jiggle
statement will call the Common Jiggle
:
common subroutine jiggle ... end subroutine ... function wiggle is float local subroutine jiggle ... end subroutine jiggle ... call jiggle ... end subroutine wiggle
A Call %jiggle
statement will call the Local Jiggle
,
as will Call %(local):jiggle
or
even %(local):jiggle
.
Common Expose
You can easily replace simple subroutines with Common subroutines. Where your code calls a simple subroutine from inside a complex subroutine or method, you can use an "exposed" Common subroutine.
For example, suppose your site uses simple subroutines containing the application code, like the following:
TALLY: SUBROUTINE ... END SUBROUTINE
In the application code, you can change the way the above subroutine is initially generated to this:
common subroutine tally expose ... end subroutine
Your existing CALL TALLY
statements will subsequently
invoke the tally
method.
The keyword Expose gives the tally
method
access to the variables in the request that are declared outside and
prior to tally
.
Exposed Common subroutines are better than simple subroutines, which essentially they replace. The specific advantage of this is that you can formally differentiate between those variables that have entire program scope versus those that are truly local to the common subroutine.
Using Common aliases
You can declare a Common Alias for a shared, non-enhancement property. The most likely use of this alias is to provide a property inside a class that replaces a common variable. This might be advantageous by providing access to private class variables from within the property:
class util public shared ... property goofy is longstring ... end public shared ... end class ... common alias pluto for (util):goofy ... %pluto is common ...
Common alias declaration syntax
The format of the Common Alias statement is straightforward:
Common Alias aliasName For (class):sharedProperty
Where:
aliasName | The name of the shared method alias. |
---|---|
class | The name of the class in which sharedProperty is a member. |
sharedProperty | The name of the Property for which the alias is to be used. |
The following example shows two common properties sharing private data via a class. In the example, you enforce a condition that the request is cancelled if the sum of two common variables exceeds 100:
b class util public shared property foo is float property bar is float end public shared private shared variable shadowFoo is float variable shadowBar is float end private shared property foo is float get return %shadowFoo end get set assert (%foo + %shadowBar) le 100 %shadowFoo = %foo end set end property property bar is float get return %shadowBar end get set assert (%bar + %shadowFoo) le 100 %shadowBar = %bar end set end property end class common alias foo for (util):foo common alias bar for (util):bar %foo is common %bar is common %foo = 22 %bar = 33 printText {~} = {%foo}, {~} = {%bar} %foo = 44 %bar = 55 printText {~} = {%foo}, {~} = {%bar} %foo = 66 %bar = 77 printText {~} = {%foo}, {~} = {%bar} end
The result is:
%foo = 22, %bar = 33 %foo = 44, %bar = 55 *** 1 CANCELLING REQUEST: MSIR.0491: Assert: line 17 ...