Local and Common entities

From m204wiki
Jump to navigation Jump to search

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:

Subroutine Returns no value.
Function Returns a value, but cannot be set to a value.
Property Can return a value or be set to a value.

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.

Persistent and Shared local variables

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.

Syntax for Persistent or Shared

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 throughout MonthlyPayment, which may be inviting a problem.
  • Making Amount exposed opens the door to problems or bugs caused by variables shared between Amount and MonthlyPayment.

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.

  1. 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

  2. In the following example, local alias Foolish is used for public subroutine Silly in class Absurd:

    class absurd ... public ... subroutine silly ... end public ... end class ... local alias (absurd):foolish for silly ... %whacky is object silly ... %whacky:foolish

  3. 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

  4. 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 keyword Local, 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

  5. 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).
  • Images defined within the subroutine are not automatically initialized. A PREPARE IMAGE must be used to ensure the image is initialized.

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 ...

See also