Methods: Difference between revisions

From m204wiki
Jump to navigation Jump to search
 
(35 intermediate revisions by 8 users not shown)
Line 1: Line 1:
As discussed in [[Classes and Objects]], a Class block can contain declaration blocks and/or '''method''' definitions.   
As discussed in [[Classes and Objects]], a <var>Class</var> block can contain declaration blocks and/or '''method''' definitions. This page describes the characteristics of these class methods and
 
their definitions. For information about defining special-purpose methods that are less broadly applicable and therefore not part of the class, see [[Local and Common entities]].   
=Method definition syntax=


==Method definition syntax==
All declaration blocks must appear before any method definitions. A method definition consists of the method header, which is basically a reiteration of the method declaration, the method body (code), and an End method statement:
All declaration blocks must appear before any method definitions. A method definition consists of the method header, which is basically a reiteration of the method declaration, the method body (code), and an End method statement:
<p class="syntax"><span class="term">methodHeader</span>
<p class="syntax"><span class="term">methodHeader</span>
Line 12: Line 12:
The ''methodHeader'' must nearly match the method declaration (which is described in detail in [[Classes and Objects#Method declarations and method types|Method declarations and method types]]), except:
The ''methodHeader'' must nearly match the method declaration (which is described in detail in [[Classes and Objects#Method declarations and method types|Method declarations and method types]]), except:


* Named parameters do not have to appear in the same order.
<ul>
* The optional Public, Private, or Shared indication may be present on one and not the other.
<li>Named parameters do not have to appear in the same order. </li>
 
<li>The optional <var>Public</var>, <var>Private</var>, or <var>Shared</var> indication may be present on one and not the other. </li>
</ul>
 
The parameter names must be present on the method declarations (as opposed to the <var>Declare Subroutine</var> statement) and must match the parameter names in the method header. This redundancy may seem extreme, it has several benefits:
 
<ul>
<li>It ensures that a complete method description is available in both the declaration block and the method definition. The former is convenient for users of the class and someone trying to understand the class as a whole. The latter is convenient for someone looking at the method in isolation. Meaningful parameter names on the method declarations will make the method functionality much clearer than the parameter datatype alone. </li>


The parameter names must be present on the method declarations (as opposed to the Declare Subroutine statement) and must match the parameter names in the method header. This redundancy may seem extreme, it has several benefits:
<li>Complete redundancy makes it possible to cut-and-paste or copy a method declaration to the method definition, or vice versa. The method body consists of the code that implements the method or methods. </li>
</ul>


* It ensures that a complete method description is available in both the declaration block and the method definition. The former is convenient for users of the class and someone trying to understand the class as a whole. The latter is convenient for someone looking at the method in isolation. Meaningful parameter names on the method declarations will make the method functionality much clearer than the parameter datatype alone.
<div id="props"></div>
* Complete redundancy makes it possible to cut-and-paste or copy a method declaration to the method definition, or vice versa. The method body consists of the code that implements the method or methods.
<var>Functions</var> and <var>Subroutines</var> have one method, but <var>Properties</var> can have two methods: one for retrieving the property (a <var>Get</var> method), and one for setting the property (a <var>Set</var> method). The <var>Get</var> and <var>Set</var> methods are enclosed in <var>Get</var> and <var>Set</var> blocks:


Functions and Subroutines have one method, but Properties can have two methods: one for setting the property (a Get method), and one for retrieving the property (a Set method). The Get and Set methods are enclosed in Get and Set blocks:
<p class="syntax"><span class="literal">Property</span> <span class="term">name</span> <span class="squareb">[</span><span class="term">(parameters)</span><span class="squareb">]</span> <span class="squareb">[</span><span class="literal">Is</span><span class="squareb">]</span> <span class="term">type</span>
<p class="syntax"> <span class="literal">Property</span> <span class="term">name</span> <span class="squareb">[</span><span class="term">(parameters)</span><span class="squareb">]</span> <span class="squareb">[</span><span class="literal">Is</span><span class="squareb">]</span> <span class="term">type</span>
  [[#Internal parameter names|<span class="squareb">[</span><span class="term">internalNamesBlock</span><span class="squareb">]</span>]]
  [[#Internal parameter names|<span class="squareb">[</span><span class="term">internalNamesBlock</span><span class="squareb">]</span>]]
  <span class="literal">Get</span>
  <span class="literal">Get</span>
    <span class="term">getmethodBody</span>
    <span class="term">getmethodBody</span>
  <span class="literal">End Get</span> <span class="squareb">[</span><span class="term">methodName</span><span class="squareb">]</span>
  <span class="literal">End Get</span> <span class="squareb">[</span><span class="term">methodName</span><span class="squareb">]</span>
  <span class="literal">Set</span>
  <span class="literal">Set</span>
    <span class="term">setmethodBody</span>
    <span class="term">setmethodBody</span>
  <span class="literal">End Set</span> <span class="squareb">[</span><span class="term">methodName</span><span class="squareb">]</span>
  <span class="literal">End Set</span> <span class="squareb">[</span><span class="term">methodName</span><span class="squareb">]</span>
<span class="literal">End</span> <span class="literal">Property</span> <span class="squareb">[</span><span class="term">methodName</span><span class="squareb">]</span>
<span class="literal">End</span> <span class="literal">Property</span> <span class="squareb">[</span><span class="term">methodName</span><span class="squareb">]</span>
</p>
</p>
The Get and Set methods can appear in any order inside the Property definition block, though must both appear within a single property definition block. ReadOnly methods have a Get method only. WriteOnly methods have a Set method only.
The <var>Get</var> and <var>Set</var> methods can appear in any order inside the <var>Property</var> definition block, though must both appear within a single property definition block. <var>ReadOnly</var> methods have a <var>Get</var> method only. <var>WriteOnly</var> methods have a <var>Set</var> method only.
 
==Comparing methods to complex subroutines==
Methods behave very much like <var class="product">SOUL</var> [[Subroutines|complex subroutines]], and they have some important differences:
 
<ul>
<li>Methods can have optional parameters, that is, parameters not specified in the method invocation. </li>
 
<li>Method Input parameters (unless they are arrays) are passed to the method by value &mdash; by copying the corresponding argument values provided when the method is invoked, instead of working with a pointer to the argument values. This copying allows the parameters to be updated within the method without consequence to the arguments outside the method.  Method Output parameters are passed by reference &#x2014; pointers to the argument values are passed instead of argument value copies, and updates to the parameters within the method affect the actual arguments outside the method. </li>
 
<div id="methobj2"></div>
<li>Instance-specific (non-<var>Shared</var>) methods always have an implicit input parameter: the object on which they operate, also known as the '''method object'''. This implicit object can be referred to by the parameter name <var>%this</var>.
 
<p class="note">'''Note:''' It is not valid to declare a local variable named <code>%this</code> in non-<var>Shared</var> methods because it is implicitly declared by <var class="product">SOUL</var>. </p></li>
 
<li>The <var>Set</var> method in a <var>Property</var> has another implicit input parameter: the value to which the property is being set. This parameter has the same name as the property. For example, if a property is called <code>Height</code>, the input value in a <var>Set</var> method for an instance-specific (non-shared) method may be referenced as:
 
<p class="code">%this:height</p>
 
<p class="note">'''Note:''' Within the <var>Set</var> method, the input value is copied (not referenced directly), so it may be updated. </p></li>


=Comparing methods to complex subroutines=
<li>Methods, being part of a class, can access the <var>Private</var> methods and variables in a class. </li>


Methods behave very much like User Language complex subroutines, and they have some important differences:
<li>All statement labels within a method definition must be unique. </li>


* Methods can have optional parameters, that is, parameters not specified in the method invocation.
<li>Local variables in methods are always auto-initialized upon entry. They take either their explicit Initial values or implicit initial values by datatype (0 for Fixed and Float, a null string for <var>String</var> and <var>Longstring</var>, a null for Objects). </li>
* Method Input parameters (unless they are arrays) are passed to the method by value — by copying the corresponding argument values provided when the method is invoked, instead of working with a pointer to the argument values. This copying allows the parameters to be updated within the method without consequence to the arguments outside the method.  Method Output parameters are passed by reference — pointers to the argument values are passed instead of argument value copies, and updates to the parameters within the method affect the actual arguments outside the method.
* Instance-specific (non-Shared) methods always have an implicit input parameter: the object on which they operate, also known as the '''method object'''. This implicit object can be referred to by the parameter name %this.
''Note: It is not valid to declare a local variable named %this in non-Shared methods because it is implicitly declared by the Janus SOAP ULI.''
* The Set method in a Property has another implicit input parameter — the value to which the property is being set. This parameter has the same name as the property. For example, if a property is called Height, the input value in a Set method for an instance-specific (non-shared) method may be referenced as


%this:height.
<li>Local variables are always stacked for recursive entry to methods. That is, if a method is called directly by itself or indirectly by other methods, the subsequent executions get their own copies of all local variables. </li>


''Note: Within the Set method, the input value is copied (not referenced directly), so it may be updated.''
<li>Object variables and <var>Longstrings</var> are automatically cleaned up on exit from methods. Object variables are set to null, and if a variable is the last reference to an object, the object is discarded (see, [[Object variables]]). <var>Longstrings</var> are set to null, and any CCATEMP pages associated with the <var>Longstrings</var> are freed. </li>


* Methods, being part of a class, can access the Private methods and variables in a class.
<li>Methods that return a value must have the return value indicated on the <var>Return</var> statement. </li>
* All statement labels within a method definition must be unique.
</ul>
* Local variables in methods are always auto-initialized upon entry. They take either their explicit Initial values or implicit initial values by datatype (0 for Fixed and Float, a null string for String and Longstring, a null for Objects).
* Local variables are always stacked for recursive entry to methods. That is, if a method is called directly by itself or indirectly by other methods, the subsequent executions get their own copies of all local variables.
* Object variables and longstrings are automatically cleaned up on exit from methods. Object variables are set to null, and if a variable is the last reference to an object, the object is discarded (see, [[Object variables]]). Longstrings are set to null, and any CCATEMP pages associated with the longstrings are freed.
* Methods that return a value must have the return value indicated on the Return statement.


=Examples of method definitions=
==Examples of method definitions==


====Function====
The following is an example of a Function that adds an amount to a private variable and returns the new value:
The following is an example of a Function that adds an amount to a private variable and returns the new value:


class account
<p class="code">class account
  private
  private
    variable balance is fixed dp 2
    variable balance is fixed dp 2
  end private
  end private
  public
  public
    function adjustBalance(%amount is fixed dp 2) -
    function adjustBalance(%amount is fixed dp 2) -
          is fixed dp 2 callable
        is fixed dp 2 callable
  end public
  end public
   
   
  function adjustBalance(%amount is fixed dp 2) is fixed dp 2 callable
  function adjustBalance(%amount is fixed dp 2) is fixed dp 2 callable
    %this:balance = %this:balance + %amount
    %this:balance = %this:balance + %amount
    return %this:balance
    return %this:balance
  end function
  end function
end class
end class
  ...
  ...
%myAccount is object account
%myAccount is object account
  ...
  ...
%balance = %myAccount:adjustBalance(50.00)
%balance = %myAccount:adjustBalance(50.00)
</p>
If the application does not need the new balance resulting from the <code>AdjustBalance</code>, the last line could be written:


If the application does not need the new balance resulting from the AdjustBalance, the last line could be written:
<p class="code">%myAccount:adjustBalance(50.00) </p>


%myAccount:adjustBalance(50.00)
As when invoking <var>Variable</var> members of a class, the colon (<tt>:</tt>) that separates the object variable <code>%myaccount</code> and the method <code>adjustBalance</code>, above, may alternatively be specified with a single blank before and/or after it.


As when invoking Variable members of a class, the colon (:) that separates the object variable %myaccount and the method adjustBalance, above, may alternatively be specified with a single blank before and/or after it.
====Property====
The following example illustrates a <var>Property</var> that returns or sets a temperature in Fahrenheit, and that uses a public variable to retrieve or set the Celsius temperature.  


The following example illustrates a property that returns or sets a temperature in Fahrenheit, and that uses a public variable to retrieve or set the Celsius temperature. class thermometer
<p class="code">class thermometer


public
public
  variable celsius is float
  variable celsius is float
  property fahrenheit is float
  property fahrenheit is float
end public
end public
property fahrenheit is float
property fahrenheit is float
  get
  get
    return (1.8 * %this:celsius) + 32
    return (1.8 * %this:celsius) + 32
  end get
  end get
  set
  set
    %this:celsius = (%fahrenheit - 32) / 1.8
    %this:celsius = (%fahrenheit - 32) / 1.8
  end set
  end set
  end property fahrenheit
end property fahrenheit
end class
end class
   
   
%temp is object thermometer
%temp is object thermometer
%temp = new
  ...
  ...
%temp:fahrenheit = %input
%temp:fahrenheit = %input
  ...
  ...
print 'Temperature fahrenheit: ' %temp:fahrenheit
print 'Temperature fahrenheit: ' %temp:fahrenheit
print 'Temperature celsius: ' %temp:celsius
print 'Temperature celsius: ' %temp:celsius
</p>


Inside a non-shared method, references to the implicit input object, %this, can omit the “this:and refer to public and private class variables as if they were local variables. For example, the above Fahrenheit property could have been written as:
Inside a non-shared method, references to the implicit input object, <code>%this</code>, can omit the &#x201C;this:&#x201D; and refer to public and private class variables as if they were local variables. For example, the above <code>Fahrenheit</code> property could have been written as:


property fahrenheit is float
<p class="code">property fahrenheit is float
get
get
return (1.8 * %celsius) + 32
return (1.8 * %celsius) + 32
end get
end get
  ...
  ...
end property fahrenheit
end property fahrenheit
 
</p>
Nevertheless, the “this:can be specified to make clear that a reference is to a class variable or to distinguish a class variable from a local variable:
Nevertheless, the <code>this:</code> can be specified to make clear that a reference is to a class variable or to distinguish a class variable from a local variable:


property fahrenheit is float
<p class="code">property fahrenheit is float
  ...
  ...
  set
  set
    %celsius is fixed dp 2
    %celsius is fixed dp 2
    %this:celsius = (%fahrenheit - 32) / 1.8
    %this:celsius = (%fahrenheit - 32) / 1.8
    %celsius = %this:celsius
    %celsius = %this:celsius
    print 'Temperature changed to ' %celsius
    print 'Temperature changed to ' %celsius
  end set
  end set
end property fahrenheit
end property fahrenheit </p>


Because a local variable is accessed instead of a class variable by the same name, the %this” is required to reference the class variable inside a class method. This precedence of local variables ahead of class variables means that public or private variables can be added to a class without fear of “breaking” methods that might have local variables with the same name. This might be important in very large classes with many methods. There are other cases where the %this” is required to access the method object.
Because a local variable is accessed instead of a class variable by the same name, the <code>%this</code> is required to reference the class variable inside a class method. This precedence of local variables ahead of class variables means that public or private variables can be added to a class without fear of &#x201C;breaking&#x201D; methods that might have local variables with the same name. This might be important in very large classes with many methods. There are other cases where the <code>%this</code> is required to access the method object.


The following is an example of a subroutine that sets a private class variable and then displays all private variables in the class:
====Subroutine====
The following is an example of a <var>Subroutine</var> that sets a private class variable and then displays all private variables in the class:


class comic
<p class="code">class comic
  private
  private
    variable name is string len 32
    variable name is string len 32
    variable pratfalls is fixed
    variable pratfalls is fixed
    variable trademark is longstring
    variable trademark is longstring
  end private
  end private
  public
  public
    subroutine display(%newName is string len 32)
    subroutine display(%newName is string len 32)
  end public
  end public
  subroutine display(%newName is string len 32)
  subroutine display(%newName is string len 32)
    %name = %newName
    %name = %newName
    print 'Name = ' %name
    print 'Name = ' %name
    print 'Pratfalls = ' %pratfalls
    print 'Pratfalls = ' %pratfalls
    print 'Trademark = ' %trademark
    print 'Trademark = ' %trademark
  end subroutine display
  end subroutine display
end class
end class
%stooge is object comic
%stooge is object comic
  ...
  ...
%stooge:display('Curly')
%stooge:display('Curly')
 
</p>
=Optional and default parameters=


==<b id="optionalParams"></b>Optional and default parameters==
Methods support optional parameters, that is, parameters that do not need to be passed on every invocation of the method. For example, suppose a function returns the number of items in a bin. And suppose that function can be passed a number of items to add to the bin. Usually when the function is invoked, no items are added, but once in a while, they are. So the function can be declared as follows:
Methods support optional parameters, that is, parameters that do not need to be passed on every invocation of the method. For example, suppose a function returns the number of items in a bin. And suppose that function can be passed a number of items to add to the bin. Usually when the function is invoked, no items are added, but once in a while, they are. So the function can be declared as follows:


class bin
<p class="code">class bin
  ...
  ...
public
public
  ...
  ...
function numberOfItems(%add is fixed optional)
function numberOfItems(%add is fixed optional)
  ...
  ...
end public
end public
  ...
  ...
end class
end class </p>


Given this declaration, the function could be invoked as follows:
Given this declaration, the function could be invoked as follows:


%yellow is object bin
<p class="code">%yellow is object bin
  ...
  ...
%yellow = new
%yellow = new
  ...
  ...
print %yellow:numberOfItems
print %yellow:numberOfItems </p>


It can also be invoked as follows:
It can also be invoked as follows:


print %yellow:numberOfItems()
<p class="code">print %yellow:numberOfItems() </p>


And, finally, in cases where a value is to be passed to the method, it could be invoked as follows:
And, finally, in cases where a value is to be passed to the method, it could be invoked as follows:


%items = %yellow:numberOfItems(%number)
<p class="code">%items = %yellow:numberOfItems(%number) </p>


When a parameter is optional and it is not passed by the invoker, that parameter has a standard value, based on its datatype, inside the method. This standard value is the standard initial value for a variable of the parameter's datatype. These initial values for each datatype are the following:
When a parameter is optional and it is not passed by the invoker, that parameter has a standard value, based on its datatype, inside the method. This standard value is the standard initial value for a variable of the parameter's datatype. These initial values for each datatype are the following:


String A null (zero-length) string.
<dl>
Longstring A null (zero-length) string.
<dt>String <dd>A null (zero-length) string.
Float 0.
<dt>Longstring <dd>A null (zero-length) string.
Fixed 0.
<dt>Float <dd>0.
Object Null.
<dt>Fixed <dd>0.
<dt>Object <dd>Null.  


''Note: Because specifying the Optional keyword for an Object parameter means that the absence of the parameter presents a null object parameter variable to the method, Optional implies the AllowNull keyword and, in fact, it cannot be specified along with AllowNull.''
<p class="note"><b>Note:</b> Because specifying the <var>Optional</var> keyword for an <var>Object</var> parameter means that the absence of the parameter presents a null object parameter variable to the method, <var>Optional</var> implies the <var>AllowNull</var> keyword and, in fact, it cannot be specified along with <var>AllowNull</var>. </p>


Structure Each item in the structure is given its default initial value, which is the same as indicated in this list for the other datatypes, unless an Initial clause was used in a structure item declaration in the structure definition. So, if a method is defined as follows:  
<dt>Structure <dd>Each item in the structure is given its default initial value, which is the same as indicated in this list for the other datatypes, unless an Initial clause was used in a structure item declaration in the structure definition. So, if a method is defined as follows:  


function square(%number is float optional)
<p class="code">function square(%number is float optional)
  ...
  ...
return %number * %number
return %number * %number
  ...
  ...
end function
end function </p>


The following will print “0”:
The following will print <code>0</code>:


print %object:square
<p class="code">print %object:square </p>
</dl>


For the “simple” datatypes Fixed, Float, or String, it is possible to specify an alternative value to be passed to a method for an unspecified argument. This is done by specifying the Default keyword instead of Optional. As of Sirius Mods 7.1, default values can also be specified for Enumeration parameters, including Booleans. The Default keyword must be followed by a string, numeric, or enumeration constant in parentheses:
For the &#x201C;simple&#x201D; datatypes <var>Fixed</var>, <var>Float</var>, or <var>String</var>, it is possible to specify an alternative value to be passed to a method for an unspecified argument. This is done by specifying the <var>Default</var> keyword instead of <var>Optional</var>. As of Sirius Mods 7.1, default values can also be specified for <var>Enumeration</var> parameters, including <var>Booleans</var>. The <var>Default</var> keyword must be followed by a string, numeric, or enumeration constant in parentheses:


function square(%number is float default(-1))
<p class="code">function square(%number is float default(-1))
  ...
  ...
return %number * %number
return %number * %number
  ...
  ...
end function
end function </p>


And the following statement prints “1” since the square of -1 is 1:
And the following statement prints <code>1</code> since the square of -1 is 1:


print %object:square
<p class="code">print %object:square </p>


The following method declaration uses an alternative default string value:
The following method declaration uses an alternative default string value:


subroutine addCustomer(%lname is string len 32 -
<p class="code">subroutine addCustomer(%lname is string len 32 -
default('***Unknown***') )
default('***Unknown***') ) </p>


The following method indicates a default value of Share for a LockStrength enumeration parameter:
The following method indicates a default value of <code>Share</code> for a <var>LockStrength</var> enumeration parameter:


function getRecord(%key is string len 10, -
<p class="code">function getRecord(%key is string len 10, -
%ls is enumeration lockstrength default(share)) -
%ls is enumeration lockstrength default(share)) -
    is object record in file foo
    is object record in file foo </p>


Optional or default parameters are allowed on any kind of method: functions, subroutines, properties, or constructors. Constructors, especially, can often benefit greatly from optional parameters as there are often extra qualifiers for newly instantiated objects that might be present, but often are not. For example, one might have a constructor for a product object that allows, but does not require, the specification of a product code:
Optional or default parameters are allowed on any kind of method: functions, subroutines, properties, or constructors. Constructors, especially, can often benefit greatly from optional parameters as there are often extra qualifiers for newly instantiated objects that might be present, but often are not. For example, one might have a constructor for a product object that allows, but does not require, the specification of a product code:


constructor new(%productCode is string len 8 optional)
<p class="code">constructor new(%productCode is string len 8 optional) </p>


In this example, the New function could then be invoked with or without the optional
In this example, the <code>New</code> function could then be invoked with or without the optional
product code:


product code:
<p class="code">%cake = new
%cake = new
  ...
  ...
%pie = new('31415929')
%pie = new('31415929') </p>
 
Before Model 204 V7.8, output parameters could not be optional or have default values. In Model 204 V7.8, output parameters can be specified as <var>Optional</var> and a default value could be specified with <var>Default</var>. One related change is that if a <var>Len</var> value is specified with <var>Optional</var> or <var>Default</var> <var>Output</var>, it indicates the length of the hidden target for the output parameter if none was passed. For all other <var>Output</var> paranmeters, <var>Len</var> is meaningless, just as it was before Model 204 V7.8. The following is an example of the use of an optional output parameter:
<p class="code">function getRemote( -
  %target is string len 32, %errorReason is string len 80 optional output -
) is float
  ...
</p>
In this example, if <code>%errorReason</code> is not specified in a call and then set to a value longer than 80 bytes it wil be truncated to 80 bytes. However, if a string of a different length is passed, the length of that string determines truncation behavior.
 
===<b id="isPresent"></b>Is [Not] Present test for optional or default parameter===


Generally, it is sufficient for a method to simply use the replacement value for an optional or default parameter without caring whether that value was specified explicitly or generated implicitly. For example, if a method is declared as follows:
Generally, it is sufficient for a method to simply use the replacement value for an optional or default parameter without caring whether that value was specified explicitly or generated implicitly. For example, if a method is declared as follows:


subroutine increment(%amount is fixed default(1))
<p class="code">subroutine increment(%amount is fixed default(1)) </p>


It probably does not matter whether it is invoked as
It probably does not matter whether it is invoked as


%object:increment(1)
<p class="code">%object:increment(1) </p>


or
or


%object:increment
<p class="code">%object:increment </p>


However, there may be cases where a method wants to distinguish between the two cases. For example, consider the following class:
However, there may be cases where a method wants to distinguish between the two cases. For example, consider the following class:


class incident
<p class="code">class incident
  public
  public
    subroutine setComment( -
    subroutine setComment( -
      %comment is string len 64 optional)
      %comment is string len 64 optional)
      ...
      ...
  end public
  end public
  private
  private
    variable haveComment is float
    variable haveComment is float
    variable comment is string len 64
    variable comment is string len 64
    ...
    ...
  end private
  end private
  ...
  ...
end class
end class </p>


A null string might be a valid comment, but it also might be useful to be able to use the SetComment method defined above to indicate that there is no comment. So, it might be necessary to distinguish the following two invocations:
A null string might be a valid comment, but it also might be useful to be able to use the <code>SetComment</code> method defined above to indicate that there is no comment. So, it might be necessary to distinguish the following two invocations:


%object:setComment
<p class="code">%object:setComment
  ...
  ...
%object:setComment('')
%object:setComment('') </p>


To make this possible, the Janus SOAP ULI provides the Is Present and Is Not Present tests for optional and default method parameters:
To make this possible, SOUL provides the <var>Is Present</var> and <var>Is Not Present</var> tests for optional and default method parameters:


subroutine setComment(%comment is string len 64 optional)
<p class="code">subroutine setComment(%comment is string len 64 optional)
  if %comment is present then
  if %comment is present then
    %this:haveComment = 1
    %this:haveComment = 1
    %this:comment = %comment
    %this:comment = %comment
  else
  else
    %this:haveComment = 0
    %this:haveComment = 0
    %this:comment = ''
    %this:comment = ''
  end if
  end if
end subroutine setComment
end subroutine setComment </p>


The Is Present and Is Not Present tests can be especially useful in code where the default value is not a constant. The most common example of this is a date parameter that is usually the current date, but not always. For example, consider a constructor that sets the start date for a transaction to the current date, unless an optional date is passed
The <var>Is Present</var> and <var>Is Not Present</var> tests can be especially useful in code where the default value is not a constant. The most common example of this is a date parameter that is usually the current date, but not always. For example, consider a constructor that sets the start date for a transaction to the current date, unless an optional date is passed
to the constructor:
to the constructor:


constructor new(%startDate is string len 8 optional)
<p class="code">constructor new(%startDate is string len 8 optional)
if %startDate is not present then
if %startDate is not present then
  %startDate = $sir_date('YYYYMMDD')
  %startDate = $sir_date('YYYYMMDD')
end if
end if  
  ...
  ... </p>


While the same thing could probably be accomplished, in this case, by checking for a null value, it is a bit neater to check for the presence of the parameter.
While the same thing could probably be accomplished, in this case, by checking for a null value, it is a bit neater to check for the presence of the parameter.


=Named parameters=
==Named parameters==
 
Methods support <b>named parameters</b>, that is, parameters that are passed by name rather than position. For example, one might have a method called <code>Roundabout</code> that has a parameter that specifies a value for an exit. It might be invoked as follows:
As of Sirius Mods version 6.7, methods support named parameters, that is, parameters that are passed by name rather than position. For example, one might have a method called Roundabout that has a parameter that specifies a value for an exit. It might be invoked as follows:
<p class="code">%bypass:roundabout('KIDLINGTON')
<p class="code"> %bypass:roundabout('KIDLINGTON')
</p>
</p>
If the first parameter for this method were allowed to be invoked by name, this method might also be invoked as:
If the first parameter for this method were allowed to be invoked by name, this method might also be invoked as:
<p class="pre"> %bypass:roundabout(exit='KIDLINGTON')
<p class="code">%bypass:roundabout(exit='KIDLINGTON')
</p>
</p>
As this example illustrates, a named parameter is indicated by the name of the parameter followed by an equal sign, which is then followed by any expression that is to be assigned to the parameter.
As this example illustrates, a named parameter is indicated by the name of the parameter followed by an equal sign, which is then followed by any expression that is to be assigned to the parameter.


Named parameters are supported for both system and User Language methods.
Named parameters are supported for both system and user-written methods.


Arguments that are passed by name are called named arguments, while those that are not are called positional arguments, because their meaning is determined by their position in an argument list. While a method can be invoked with both positional and named arguments, all the positional arguments must precede the named arguments. That is, the following is valid because all the positional arguments precede the named ones:
Arguments that are passed by name are called <b>named</b> arguments, while those that are not are called <b>positional</b> arguments, because their meaning is determined by their position in an argument list. While a method can be invoked with both positional and named arguments, all the positional arguments must precede the named arguments. That is, the following is valid because all the positional arguments precede the named ones:
<p class="code"> %mini:addPetrol(24, 'Stratford', price=89.5, brand='BP')
<p class="code">%mini:addPetrol(24, 'Stratford', price=89.5, brand='BP')
</p>
</p>
But the following is invalid because the named parameter litres precedes the positional parameter 'Stratford'.
But the following is invalid because the named parameter litres precedes the positional parameter <code>Stratford</code>.
<p class="code"> %mini:addPetrol(litres=24, 'Stratford', price=89.5)
<p class="code">%mini:addPetrol(litres=24, 'Stratford', price=89.5)
</p>
</p>
A name on an argument can be allowed or required. If allowed, the argument can be specified either positionally or by name. If required, the argument can only be specified by name. Whether the name on an argument is allowed or required has nothing to do with whether the argument is required. That is, just as positional parameters can be required, Optional, or Default, so too can named parameters. That said, named parameters will tend to be optional, since required parameters usually have a fixed position in a parameter list.
A name on an argument can be allowed or required. If allowed, the argument can be specified either positionally or by name. If required, the argument can only be specified by name. Whether the name on an argument is allowed or required has nothing to do with whether the argument is required. That is, just as positional parameters can be required, Optional, or Default, so too can named parameters. That said, named parameters will tend to be optional, since required parameters usually have a fixed position in a parameter list.


As noted before, system methods can have named parameters. To determine which parameters on a system method are positional, name allowed, or name required, see the documentation for that method. An example of a system method with a name required parameter is the ParseLines method, which has a name required (but optional) StripTrailingNull parameter:
As noted before, system methods can have named parameters. To determine which parameters on a system method are positional, name allowed, or name required, see the documentation for that method. An example of a system method with a name required parameter is the <var>ParseLines</var> method, which has a name required (but optional) <var>StripTrailingNull</var> parameter:
<p class="code"> %list:parseLines(%string, stripTrailingNull=false)
<p class="code"> %list:parseLines(%string, stripTrailingNull=false)
</p>
</p>
For User Language methods, the distinction between positional, name allowed, and name required parameters is made on the method declaration and definition header. By default, the parameters in User Language methods are positional. For example, in the following method declaration, all parameters are positional:
For user-written methods, the distinction between positional, name allowed, and name required parameters is made on the method declaration and definition header. By default, the parameters in user-written methods are positional. For example, in the following method declaration, all parameters are positional:
<p class="code">subroutine display(%customerId is string len 10, -
<p class="code">subroutine display(%customerId is string len 10, -
                   %startyear is float, -
                   %startyear is float, -
Line 325: Line 362:
</p>
</p>
So the method might be invoked as follows:
So the method might be invoked as follows:
<p class="code"> %cust:display(%crn, %year, true, , true)
<p class="code">%cust:display(%crn, %year, true, , true)
</p>
</p>
To indicate that a name can be used for a parameter, the <var>NameAllowed</var> keyword should be specified on the parameter declaration:
To indicate that a name can be used for a parameter, the <var>NameAllowed</var> keyword should be specified on the parameter declaration:
Line 335: Line 372:
</p>
</p>
So that the method could then be invoked either as before, or as follows:
So that the method could then be invoked either as before, or as follows:
<p class="pre">%cust:display(%crn, %year, true, , showMedical=true)
<p class="code">%cust:display(%crn, %year, true, , showMedical=true)
</p>
</p>
'''Note:''' Even though the parameter name is declared with a leading percent character (%), the name of the parameter when invoking the method should exclude the percent character.
<p class="note">'''Note:''' Even though the parameter name is declared with a leading percent character (<tt>%</tt>), the name of the parameter when invoking the method should exclude the percent character. </p>


If the <var>NameRequired</var> keyword is specified on a parameter definition, the parameter must always be passed as a named argument rather than a positional one:
If the <var>NameRequired</var> keyword is specified on a parameter definition, the parameter must always be passed as a named argument rather than a positional one:
Line 383: Line 420:
</p>
</p>
All invocations of the method would then have to use names:
All invocations of the method would then have to use names:
<p class="code"> %mg:add(petrol=18)
<p class="code">%mg:add(petrol=18)
</p>
</p>
If after some experience, it is determined that one parameter is much more commonly used than another, that parameter can be changed to a NameAllowed parameter:
If after some experience, it is determined that one parameter is much more commonly used than another, that parameter can be changed to a <var>NameAllowed</var> parameter:
<p class="code">subroutine add(%petrol is float optional <var>NameAllowed</var>, -
<p class="code">subroutine add(%petrol is float optional <var>NameAllowed</var>, -
               %oil is float optional nameRequired)
               %oil is float optional nameRequired)
</p>
</p>
When that change is made, invocations of the method can use the NameAllowed parameter positionally:
When that change is made, invocations of the method can use the <var>NameAllowed</var> parameter positionally:
<p class="code">%mg:add(18)
<p class="code">%mg:add(18)
</p>
</p>
Note that this delaying tactic can even be useful with non-optional parameters, if there is a possibility that some of the non-optional parameters might be made optional some day perhaps after appropriate defaults are determined.
Note that this delaying tactic can even be useful with non-optional parameters, if there is a possibility that some of the non-optional parameters might be made optional some day &mdash; perhaps after appropriate defaults are determined.
 
Finally, named parameters can be used to &#x201C;rescue&#x201D; a (retrospectively) bad decision about parameter order. For example, if after some use, it is determined that for a method with many parameters, the fifth parameter is more often specified than any of the earlier parameters, it can be turned into a <var>NameAllowed</var> parameter, making it possible to specify that parameter without having to specify the earlier parameters. All this said, named parameters are not a panacea. There are some drawbacks to using named parameters:


Finally, named parameters can be used to “rescue” a (retrospectively) bad decision about parameter order. For example, if after some use, it is determined that for a method with many parameters, the fifth parameter is more often specified than any of the earlier parameters, it can be turned into a NameAllowed parameter, making it possible to specify that parameter without having to specify the earlier parameters. All this said, named parameters are not a panacea. There are some drawbacks to using named parameters:
<ul>
<li>They make method invocations more verbose, sometimes with little or no benefit. This is especially true for methods with one or two parameters where the meaning of the parameter(s) are obvious from the name of the method.


* They make method invocations more verbose, sometimes with little or no benefit. This is especially true for methods with one or two parameters where the meaning of the parameter(s) are obvious from the name of the method.
<li>Time and care must be taken to chose good parameter names. Once a parameter name is chosen for a named parameter, it can be quite difficult to change it, as this would require changing all code that specifies that parameter name in a method invocation.
* Time and care must be taken to chose good parameter names. Once a parameter name is chosen for a named parameter, it can be quite difficult to change it, as this would require changing all code that specifies that parameter name in a method invocation.
</ul>


So, as with most programming constructs, the use of named parameters is an art rather than a science. As named parameters should be used where appropriate, they should be avoided where inappropriate. But other than the vague and not inviolate principle that named parameters are more likely to be useful in methods with more parameters, the appropriateness of named parameters is a subjective matter.
So, as with most programming constructs, the use of named parameters is an art rather than a science. As named parameters should be used where appropriate, they should be avoided where inappropriate. But other than the vague and not inviolate principle that named parameters are more likely to be useful in methods with more parameters, the appropriateness of named parameters is a subjective matter.


Note: A final point should be made about named parameters: their syntax is somewhat of an inconsistency in User Language. In most contexts where User Language expressions are allowed, an equal sign is treated as a comparison operator.
<p class="note">'''Note:''' A final point should be made about named parameters: their syntax is somewhat of an inconsistency in <var class="product">User Language</var>. In most contexts where user-written expressions are allowed, an equal sign is treated as a comparison operator. </p>


For example, in the following statement
For example, in the following statement the variables <code>%value</code> and <code>%crn</code> are compared, and, if equal, the global <code>CRN</code> is set to 1, otherwise it is set to 0:
<p class="code">$setg('CRN', %value=%crn)
<p class="code">$setg('CRN', %value=%crn)
</p>
</p>
the variables %variable and %crn are compared, and, if equal, the global CRN is set to 1, otherwise it is set to 0. Of course, if either %value or %crn was not defined, this statement would generate a compilation error.
If <code>%value</code> or <code>%crn</code> were not defined, this statement would generate a compilation error.


Given this syntactic structure, one might expect that a named argument in a method invocation would be interpreted as a comparison of a fieldname with a value. For example, one might expect the argument to the New method to be the result of a comparison between field Host and the variable %target:
Given this syntactic structure, one might expect that a named argument in a method invocation would be interpreted as a comparison of a field name with a value. For example, one might expect the argument to the <var>New</var> method to be the result of a comparison between field <code>Host</code> and the variable <code>%target</code>:
<p class="code"> %socket = new(host=%target)
<p class="code">%socket = new(host=%target)
</p>
</p>
The reality is that the use of the equal sign is almost always limited to conditions in an If statement, so one probably wouldn't expect to find a comparison in the above context. The Janus SOAP ULI takes one step further by interpreting any single token followed by an equal sign in a method parameter as a named argument, whether or not what is to the left of the equal sign is a valid parameter name. For example, the following will produce a compilation error, even if %input is a defined variable, and even though it is not a valid parameter name:
The reality is that the use of the equal sign is almost always limited to conditions in an <var>If</var> statement, so one probably wouldn't expect to find a comparison in the above context. Object-oriented SOUL takes one step further by interpreting any single token followed by an equal sign in a method parameter as a named argument, whether or not what is to the left of the equal sign is a valid parameter name. For example, the following will produce a compilation error, even if <code>%input</code> is a defined variable, and even though it is not a valid parameter name:
<p class="code">%object:doIfTrue(%input = 1)
<p class="code">%object:doIfTrue(%input = 1)
</p>
</p>
Line 419: Line 459:
or, the comparison can be wrapped in an extra parenthesis:
or, the comparison can be wrapped in an extra parenthesis:
<p class="code">%object:doIfTrue((%input eq 1))
<p class="code">%object:doIfTrue((%input eq 1))
</p>
</p>


=Internal parameter names=
==<b id="Internal parameter names"></b>Parameter description guidelines==
 
A method declaration contains a description of the method's parameters. A parameter description includes the parameter name, type, and attributes (<var>Optional</var>, <var>AllowNull</var>, and so on). The names should be descriptive, as this makes it easier for users of a method to understand what the method does by looking at its declaration. For example, the following method declaration
A method declaration contains a description of method parameters. This description includes the parameter type, parameter attributes (Optional, AllowNull, and so on), and parameter names. The parameter names should be descriptive, as this makes it easier for users of a method to understand what the method does by looking at its declaration. For example, the following method declaration


subroutine fill(%petrol is float, %oil is float)
<p class="code">subroutine fill(%petrol is float, %oil is float)</p>


is much more helpful than this declaration:
is much more helpful than this declaration:


subroutine fill(%x is float, %y is float)
<p class="code">subroutine fill(%x is float, %y is float)</p>


With the availability of named parameters, the use of meaningful parameter names becomes even more important, because the parameter names will actually appear in the method invocations:
With the availability of named parameters, the use of meaningful parameter names becomes even more important, because the parameter names will actually appear in the method invocations:


%multipla:fill(oil=0.5)
<p class="code">%multipla:fill(oil=0.5)</p>
===<b id="intNamesStmt"></b>Mapping parameter names in declarations to names within methods===
It is possible that the ideal names for method declarations are not the ideal names for the parameters within the actual methods themselves. For example, one might have a standard that all parameters should start with <code>parm.</code> in method code, so that it is easy to tell which variables are parameters. If this standard is extended to the names on the declaration, the result is ugly and redundant parameter names in declarations and method invocations:


However, it is possible that the ideal names for method declarations are not the ideal names for the parameters inside the actual methods themselves. For example, one might have a standard that all parameters should start with “parm.” in method code, so that it is easy to tell which variables are parameters. If this standard is extended to the names on the declaration, the result is ugly and redundant parameter names in declarations and method invocations:
<p class="code">%multipla:fill(parm.oil=0.5)</p>


%multipla:fill(parm.oil=0.5)
The <var>InternalNames</var> block is provided to allow mapping of parameter names on declarations to another name to be used within the method, making it possible to use the optimal names on method declarations and within methods, even if the two names are not the same.


The InternalNames statement is provided to allow mapping of parameter names on declarations to another name to be used inside the method, making it possible to use the optimal names on method declarations and inside methods, even if the two names are not the same.
The syntax of the <var>InternalNames</var> block is:


The InternalNames statement marks the start of a block, where each line has the following syntax:
<p class="syntax"><span class="literal">InternalNames</span>
 
<span class="term">newName</span> <span class="squareb">[</span><span class="literal">Is</span><span class="squareb">]</span> <span class="term">parameterName</span>
newName [is] parameterName
...
<span class="literal">End InternalNames</span>
</p>


Where:
Where:


;newName  
<table>
: The name to be assigned to that parameter for internal use.  
<tr><th>newName</th><td>The name to be assigned to that parameter for internal use.</td></tr>
; parameterName  
<tr><th>parameterName</th><td>The name of the parameter as it appears in the method declaration.</td></tr>
: The name of the parameter as it appears in the method declaration.
</table>


The InternalNames block must be terminated by an End InternalNames line. The InternalNames statement must be the first statement after a method definition header, which means that for properties it must appear before the Get and Set blocks.
The <var>InternalNames</var> statement must be the first statement after a method definition header, which means that for properties it must appear before the <var>Get</var> and <var>Set</var> blocks.


For properties, the InternalNames statement changes the names of parameters for both the Get and Set method. InternalNames can be used to rename implicit parameters, such as:
For properties, the <var>InternalNames</var> block changes the names of parameters for both the <var>Get</var> and <var>Set</var> methods. <var>InternalNames</var> can be used to rename implicit parameters, such as:


* %this, which is used to reference the method object.
<ul>
* The input value parameter for properties, which has the same name as the property (preceded by a percent).
<li>%this, which is used to reference the method object. </li>


In the following example, the input variables %petrol and %oil are mapped to the names %parm.petrol and %parm.oil, respectively:
<li>The input value parameter for properties, which has the same name as the property (preceded by a percent). </li>
</ul>


subroutine add(%petrol is float nameRequired optional, -
In the following example, the input variables <code>%petrol</code> and <code>%oil</code> are mapped to the names <code>%parm.petrol</code> and <code>%parm.oil</code>, respectively:
  %oil is float nameRequired optional)
  internalNames
  %parm.petrol is %petrol
  %parm.oil is %oil
end internalNames
...
%petrol = %petrol + %parm.petrol
%oil = %oil + %parm.oil


This example illustrates how if petrol and oil were private class variables, the InternalNames statement makes it possible to have parameters in a method declaration with the same name, but to avoid naming conflict or confusion between the private class variables and the parameter names.
<p class="code">subroutine add(%petrol is float nameRequired optional, -
  %oil is float nameRequired optional)
internalNames
  %parm.petrol is %petrol
  %parm.oil is %oil
end internalNames
...
%petrol = %petrol + %parm.petrol
%oil = %oil + %parm.oil
</p>


=Stringing method invocations together=
This example illustrates how if <code>petrol</code> and <code>oil</code> were private class variables, the <var>InternalNames</var> block makes it possible to have parameters in a method declaration with the same name, but to avoid naming conflict or confusion between the private class variables and the parameter names.


==Stringing method invocations together==
Often, a method will return an object, that is, an instance of a class. In such cases, it is possible to invoke another method against the result object by stringing together method invocations.
Often, a method will return an object, that is, an instance of a class. In such cases, it is possible to invoke another method against the result object by stringing together method invocations.


In the following example, the method object for the Slap subroutine is the object returned by the Call function:
In the following example, the method object for the <code>Slap</code> subroutine is the object returned by the <code>Call</code> function:


class comic
<p class="code">class comic
  public
  public
    function call(%name is string len 32) is object comic
    function call(%name is string len 32) is object comic
    subroutine slap
    subroutine slap
    ...
    ...
  end public
  end public
  ...
  ...
end class
end class
  ...
  ...
%moe is object comic
%moe is object comic
  ...
  ...
%moe:call('Curly):slap
%moe:call('Curly):slap </p>


The same is true for stringing a method invocation to an object variable in a class:
The same is true for stringing a method invocation to an object variable in a class:


class comic
<p class="code">class comic
  public
  public
    variable brother is object comic
    variable brother is object comic
    subroutine slap
    subroutine slap
    ...
    ...
  end public
  end public
  ...
  ...
end class
end class
  ...
  ...
%moe is object comic
%moe is object comic
  ...
  ...
%moe:brother:slap
%moe:brother:slap </p>


This stringing of method/variable names can be extended indefinitely and is one of the big advantages of object-oriented syntax over standard procedure syntax. Procedural syntax accomplishes the same economy using nesting:
This stringing of method/variable names can be extended indefinitely and is one of the big advantages of object-oriented syntax over standard procedure syntax. Procedural syntax accomplishes the same economy using nesting:


call $slap($brother(%moe))
<p class="code">call $slap($brother(%moe)) </p>
 
With procedural syntax, the evaluation is from the inside out and should be read that way. Evaluation of strung-together class members is left to right, and so it can be read in that more natural order. If there is more than one input object to a method, object oriented syntax must also resort to nesting, but this is relatively rare and still requires one fewer nested object than procedural syntax.
 
==See also==
<ul>
<li>[[Classes and Objects]]
<li>[[Local and Common entities]]
<li>[[Enhancement methods]]
<li>[[Print, Audit, and Trace methods for user objects]]
<li>[[Object oriented programming in SOUL]]
</ul>


With procedural syntax, the evaluation is from the inside out and should be read that way. Evaluation of strung-together class members is left to right, and so it can be read in that more natural order. If there is more than one input object to a method, objectoriented syntax must also resort to nesting, but this is relatively rare and still requires one fewer nested object than procedural syntax.
[[Category:Overviews]]
[[Category:Overviews]]
[[Category:SOUL object-oriented programming topics]]

Latest revision as of 21:55, 25 October 2019

As discussed in Classes and Objects, a Class block can contain declaration blocks and/or method definitions. This page describes the characteristics of these class methods and their definitions. For information about defining special-purpose methods that are less broadly applicable and therefore not part of the class, see Local and Common entities.

Method definition syntax

All declaration blocks must appear before any method definitions. A method definition consists of the method header, which is basically a reiteration of the method declaration, the method body (code), and an End method statement:

methodHeader [internalNamesBlock] methodBody End methodType [methodName]

The methodHeader must nearly match the method declaration (which is described in detail in Method declarations and method types), except:

  • Named parameters do not have to appear in the same order.
  • The optional Public, Private, or Shared indication may be present on one and not the other.

The parameter names must be present on the method declarations (as opposed to the Declare Subroutine statement) and must match the parameter names in the method header. This redundancy may seem extreme, it has several benefits:

  • It ensures that a complete method description is available in both the declaration block and the method definition. The former is convenient for users of the class and someone trying to understand the class as a whole. The latter is convenient for someone looking at the method in isolation. Meaningful parameter names on the method declarations will make the method functionality much clearer than the parameter datatype alone.
  • Complete redundancy makes it possible to cut-and-paste or copy a method declaration to the method definition, or vice versa. The method body consists of the code that implements the method or methods.

Functions and Subroutines have one method, but Properties can have two methods: one for retrieving the property (a Get method), and one for setting the property (a Set method). The Get and Set methods are enclosed in Get and Set blocks:

Property name [(parameters)] [Is] type [internalNamesBlock] Get getmethodBody End Get [methodName] Set setmethodBody End Set [methodName] End Property [methodName]

The Get and Set methods can appear in any order inside the Property definition block, though must both appear within a single property definition block. ReadOnly methods have a Get method only. WriteOnly methods have a Set method only.

Comparing methods to complex subroutines

Methods behave very much like SOUL complex subroutines, and they have some important differences:

  • Methods can have optional parameters, that is, parameters not specified in the method invocation.
  • Method Input parameters (unless they are arrays) are passed to the method by value — by copying the corresponding argument values provided when the method is invoked, instead of working with a pointer to the argument values. This copying allows the parameters to be updated within the method without consequence to the arguments outside the method. Method Output parameters are passed by reference — pointers to the argument values are passed instead of argument value copies, and updates to the parameters within the method affect the actual arguments outside the method.
  • Instance-specific (non-Shared) methods always have an implicit input parameter: the object on which they operate, also known as the method object. This implicit object can be referred to by the parameter name %this.

    Note: It is not valid to declare a local variable named %this in non-Shared methods because it is implicitly declared by SOUL.

  • The Set method in a Property has another implicit input parameter: the value to which the property is being set. This parameter has the same name as the property. For example, if a property is called Height, the input value in a Set method for an instance-specific (non-shared) method may be referenced as:

    %this:height

    Note: Within the Set method, the input value is copied (not referenced directly), so it may be updated.

  • Methods, being part of a class, can access the Private methods and variables in a class.
  • All statement labels within a method definition must be unique.
  • Local variables in methods are always auto-initialized upon entry. They take either their explicit Initial values or implicit initial values by datatype (0 for Fixed and Float, a null string for String and Longstring, a null for Objects).
  • Local variables are always stacked for recursive entry to methods. That is, if a method is called directly by itself or indirectly by other methods, the subsequent executions get their own copies of all local variables.
  • Object variables and Longstrings are automatically cleaned up on exit from methods. Object variables are set to null, and if a variable is the last reference to an object, the object is discarded (see, Object variables). Longstrings are set to null, and any CCATEMP pages associated with the Longstrings are freed.
  • Methods that return a value must have the return value indicated on the Return statement.

Examples of method definitions

Function

The following is an example of a Function that adds an amount to a private variable and returns the new value:

class account private variable balance is fixed dp 2 end private public function adjustBalance(%amount is fixed dp 2) - is fixed dp 2 callable end public function adjustBalance(%amount is fixed dp 2) is fixed dp 2 callable %this:balance = %this:balance + %amount return %this:balance end function end class ... %myAccount is object account ... %balance = %myAccount:adjustBalance(50.00)

If the application does not need the new balance resulting from the AdjustBalance, the last line could be written:

%myAccount:adjustBalance(50.00)

As when invoking Variable members of a class, the colon (:) that separates the object variable %myaccount and the method adjustBalance, above, may alternatively be specified with a single blank before and/or after it.

Property

The following example illustrates a Property that returns or sets a temperature in Fahrenheit, and that uses a public variable to retrieve or set the Celsius temperature.

class thermometer public variable celsius is float property fahrenheit is float end public property fahrenheit is float get return (1.8 * %this:celsius) + 32 end get set %this:celsius = (%fahrenheit - 32) / 1.8 end set end property fahrenheit end class %temp is object thermometer %temp = new ... %temp:fahrenheit = %input ... print 'Temperature fahrenheit: ' %temp:fahrenheit print 'Temperature celsius: ' %temp:celsius

Inside a non-shared method, references to the implicit input object, %this, can omit the “this:” and refer to public and private class variables as if they were local variables. For example, the above Fahrenheit property could have been written as:

property fahrenheit is float get return (1.8 * %celsius) + 32 end get ... end property fahrenheit

Nevertheless, the this: can be specified to make clear that a reference is to a class variable or to distinguish a class variable from a local variable:

property fahrenheit is float ... set %celsius is fixed dp 2 %this:celsius = (%fahrenheit - 32) / 1.8 %celsius = %this:celsius print 'Temperature changed to ' %celsius end set end property fahrenheit

Because a local variable is accessed instead of a class variable by the same name, the %this is required to reference the class variable inside a class method. This precedence of local variables ahead of class variables means that public or private variables can be added to a class without fear of “breaking” methods that might have local variables with the same name. This might be important in very large classes with many methods. There are other cases where the %this is required to access the method object.

Subroutine

The following is an example of a Subroutine that sets a private class variable and then displays all private variables in the class:

class comic private variable name is string len 32 variable pratfalls is fixed variable trademark is longstring end private public subroutine display(%newName is string len 32) end public subroutine display(%newName is string len 32) %name = %newName print 'Name = ' %name print 'Pratfalls = ' %pratfalls print 'Trademark = ' %trademark end subroutine display end class %stooge is object comic ... %stooge:display('Curly')

Optional and default parameters

Methods support optional parameters, that is, parameters that do not need to be passed on every invocation of the method. For example, suppose a function returns the number of items in a bin. And suppose that function can be passed a number of items to add to the bin. Usually when the function is invoked, no items are added, but once in a while, they are. So the function can be declared as follows:

class bin ... public ... function numberOfItems(%add is fixed optional) ... end public ... end class

Given this declaration, the function could be invoked as follows:

%yellow is object bin ... %yellow = new ... print %yellow:numberOfItems

It can also be invoked as follows:

print %yellow:numberOfItems()

And, finally, in cases where a value is to be passed to the method, it could be invoked as follows:

%items = %yellow:numberOfItems(%number)

When a parameter is optional and it is not passed by the invoker, that parameter has a standard value, based on its datatype, inside the method. This standard value is the standard initial value for a variable of the parameter's datatype. These initial values for each datatype are the following:

String
A null (zero-length) string.
Longstring
A null (zero-length) string.
Float
0.
Fixed
0.
Object
Null.

Note: Because specifying the Optional keyword for an Object parameter means that the absence of the parameter presents a null object parameter variable to the method, Optional implies the AllowNull keyword and, in fact, it cannot be specified along with AllowNull.

Structure
Each item in the structure is given its default initial value, which is the same as indicated in this list for the other datatypes, unless an Initial clause was used in a structure item declaration in the structure definition. So, if a method is defined as follows:

function square(%number is float optional) ... return %number * %number ... end function

The following will print 0:

print %object:square

For the “simple” datatypes Fixed, Float, or String, it is possible to specify an alternative value to be passed to a method for an unspecified argument. This is done by specifying the Default keyword instead of Optional. As of Sirius Mods 7.1, default values can also be specified for Enumeration parameters, including Booleans. The Default keyword must be followed by a string, numeric, or enumeration constant in parentheses:

function square(%number is float default(-1)) ... return %number * %number ... end function

And the following statement prints 1 since the square of -1 is 1:

print %object:square

The following method declaration uses an alternative default string value:

subroutine addCustomer(%lname is string len 32 - default('***Unknown***') )

The following method indicates a default value of Share for a LockStrength enumeration parameter:

function getRecord(%key is string len 10, - %ls is enumeration lockstrength default(share)) - is object record in file foo

Optional or default parameters are allowed on any kind of method: functions, subroutines, properties, or constructors. Constructors, especially, can often benefit greatly from optional parameters as there are often extra qualifiers for newly instantiated objects that might be present, but often are not. For example, one might have a constructor for a product object that allows, but does not require, the specification of a product code:

constructor new(%productCode is string len 8 optional)

In this example, the New function could then be invoked with or without the optional product code:

%cake = new ... %pie = new('31415929')

Before Model 204 V7.8, output parameters could not be optional or have default values. In Model 204 V7.8, output parameters can be specified as Optional and a default value could be specified with Default. One related change is that if a Len value is specified with Optional or Default Output, it indicates the length of the hidden target for the output parameter if none was passed. For all other Output paranmeters, Len is meaningless, just as it was before Model 204 V7.8. The following is an example of the use of an optional output parameter:

function getRemote( - %target is string len 32, %errorReason is string len 80 optional output - ) is float ...

In this example, if %errorReason is not specified in a call and then set to a value longer than 80 bytes it wil be truncated to 80 bytes. However, if a string of a different length is passed, the length of that string determines truncation behavior.

Is [Not] Present test for optional or default parameter

Generally, it is sufficient for a method to simply use the replacement value for an optional or default parameter without caring whether that value was specified explicitly or generated implicitly. For example, if a method is declared as follows:

subroutine increment(%amount is fixed default(1))

It probably does not matter whether it is invoked as

%object:increment(1)

or

%object:increment

However, there may be cases where a method wants to distinguish between the two cases. For example, consider the following class:

class incident public subroutine setComment( - %comment is string len 64 optional) ... end public private variable haveComment is float variable comment is string len 64 ... end private ... end class

A null string might be a valid comment, but it also might be useful to be able to use the SetComment method defined above to indicate that there is no comment. So, it might be necessary to distinguish the following two invocations:

%object:setComment ... %object:setComment()

To make this possible, SOUL provides the Is Present and Is Not Present tests for optional and default method parameters:

subroutine setComment(%comment is string len 64 optional) if %comment is present then %this:haveComment = 1 %this:comment = %comment else %this:haveComment = 0 %this:comment = end if end subroutine setComment

The Is Present and Is Not Present tests can be especially useful in code where the default value is not a constant. The most common example of this is a date parameter that is usually the current date, but not always. For example, consider a constructor that sets the start date for a transaction to the current date, unless an optional date is passed to the constructor:

constructor new(%startDate is string len 8 optional) if %startDate is not present then %startDate = $sir_date('YYYYMMDD') end if ...

While the same thing could probably be accomplished, in this case, by checking for a null value, it is a bit neater to check for the presence of the parameter.

Named parameters

Methods support named parameters, that is, parameters that are passed by name rather than position. For example, one might have a method called Roundabout that has a parameter that specifies a value for an exit. It might be invoked as follows:

%bypass:roundabout('KIDLINGTON')

If the first parameter for this method were allowed to be invoked by name, this method might also be invoked as:

%bypass:roundabout(exit='KIDLINGTON')

As this example illustrates, a named parameter is indicated by the name of the parameter followed by an equal sign, which is then followed by any expression that is to be assigned to the parameter.

Named parameters are supported for both system and user-written methods.

Arguments that are passed by name are called named arguments, while those that are not are called positional arguments, because their meaning is determined by their position in an argument list. While a method can be invoked with both positional and named arguments, all the positional arguments must precede the named arguments. That is, the following is valid because all the positional arguments precede the named ones:

%mini:addPetrol(24, 'Stratford', price=89.5, brand='BP')

But the following is invalid because the named parameter litres precedes the positional parameter Stratford.

%mini:addPetrol(litres=24, 'Stratford', price=89.5)

A name on an argument can be allowed or required. If allowed, the argument can be specified either positionally or by name. If required, the argument can only be specified by name. Whether the name on an argument is allowed or required has nothing to do with whether the argument is required. That is, just as positional parameters can be required, Optional, or Default, so too can named parameters. That said, named parameters will tend to be optional, since required parameters usually have a fixed position in a parameter list.

As noted before, system methods can have named parameters. To determine which parameters on a system method are positional, name allowed, or name required, see the documentation for that method. An example of a system method with a name required parameter is the ParseLines method, which has a name required (but optional) StripTrailingNull parameter:

%list:parseLines(%string, stripTrailingNull=false)

For user-written methods, the distinction between positional, name allowed, and name required parameters is made on the method declaration and definition header. By default, the parameters in user-written methods are positional. For example, in the following method declaration, all parameters are positional:

subroutine display(%customerId is string len 10, - %startyear is float, - %showFamily is enumeration boolean optional, - %showEmployer is enumeration boolean optional, - %showMedical is enumeration boolean optional)

So the method might be invoked as follows:

%cust:display(%crn, %year, true, , true)

To indicate that a name can be used for a parameter, the NameAllowed keyword should be specified on the parameter declaration:

subroutine display(%customerId is string len 10, - %startyear is float, - %showFamily is enumeration boolean optional, - %showEmployer is enumeration boolean optional, - %showMedical is enumeration boolean optional nameAllowed)

So that the method could then be invoked either as before, or as follows:

%cust:display(%crn, %year, true, , showMedical=true)

Note: Even though the parameter name is declared with a leading percent character (%), the name of the parameter when invoking the method should exclude the percent character.

If the NameRequired keyword is specified on a parameter definition, the parameter must always be passed as a named argument rather than a positional one:

subroutine display(%customerId is string len 10, - %startyear is float, - %showFamily is enumeration boolean optional, - %showEmployer is enumeration boolean optional, - %showMedical is enumeration boolean optional nameRequired)

If a parameter other than the last is specified with NameAllowed, all parameters after that are also treated as if NameAllowed were specified on their declarations. That is, the following two declarations are equivalent:

subroutine display(%customerId is string len 10, - %startyear is float, - %showFamily is enumeration boolean optional nameAllowed, - %showEmployer is enumeration boolean optional nameAllowed, - %showMedical is enumeration boolean optional nameAllowed)

subroutine display(%customerId is string len 10, - %startyear is float, - %showFamily is enumeration boolean optional nameAllowed, - %showEmployer is enumeration boolean optional, - %showMedical is enumeration boolean optional)

Similarly, a NameRequired keyword implies NameRequired for all subsequent parameters. A NameRequired parameter can follow a NameAllowed parameter though, of course, all parameters after the NameRequired parameter are also assumed to be NameRequired, whether or not that keyword is specified. A NameAllowed parameter cannot follow a NameRequired parameter. Named arguments can be specified in any order, so given the above declaration, the following two invocations are identical:

%cust:display(%crn, %year, showFamily=true, showMedical=false)

%cust:display(%crn, %year, showMedical=false, showFamily=true)

There are many reasons to use named parameters. One is to make method invocations easier to understand. If a method has many parameters, the invocations can look like:

%cust:display(%crn, %year, true, false)

And, if a programmer looking at this did not have the parameters for this method memorized, she would have to go to the method declaration to have an idea of what was being done here. But naming the arguments makes the code much clearer:

%cust:display(%crn, %year, showFamily=true, showMedical=false)

As this example illustrates, named parameters can be particularly useful for boolean switches that alter the behavior of a method, especially when there are many of these switches.

Another advantage of named parameters is that they are order independent. This means that place-holders do not have to be specified for optional parameters that are not specified on their invocation. This is especially important for methods with many optional parameters. For example, in the absence of named parameters, a method with seven optional parameters might be invoked as follows:

%mini:drive(,,,,,,,65)

Such a call can be made much easier to read, and much less error prone, by using a named parameter:

%mini:drive(speed=65)

Named parameters can also be useful to delay decisions about parameter order. That is, if a method has, say, two optional parameters, and it is not immediately obvious which one is most likely to be used more often (and thus be the first positional parameter), the parameters can be initially declared as NameRequired:

subroutine add(%oil is float optional nameRequired, - %petrol is float optional nameRequired)

All invocations of the method would then have to use names:

%mg:add(petrol=18)

If after some experience, it is determined that one parameter is much more commonly used than another, that parameter can be changed to a NameAllowed parameter:

subroutine add(%petrol is float optional NameAllowed, - %oil is float optional nameRequired)

When that change is made, invocations of the method can use the NameAllowed parameter positionally:

%mg:add(18)

Note that this delaying tactic can even be useful with non-optional parameters, if there is a possibility that some of the non-optional parameters might be made optional some day — perhaps after appropriate defaults are determined.

Finally, named parameters can be used to “rescue” a (retrospectively) bad decision about parameter order. For example, if after some use, it is determined that for a method with many parameters, the fifth parameter is more often specified than any of the earlier parameters, it can be turned into a NameAllowed parameter, making it possible to specify that parameter without having to specify the earlier parameters. All this said, named parameters are not a panacea. There are some drawbacks to using named parameters:

  • They make method invocations more verbose, sometimes with little or no benefit. This is especially true for methods with one or two parameters where the meaning of the parameter(s) are obvious from the name of the method.
  • Time and care must be taken to chose good parameter names. Once a parameter name is chosen for a named parameter, it can be quite difficult to change it, as this would require changing all code that specifies that parameter name in a method invocation.

So, as with most programming constructs, the use of named parameters is an art rather than a science. As named parameters should be used where appropriate, they should be avoided where inappropriate. But other than the vague and not inviolate principle that named parameters are more likely to be useful in methods with more parameters, the appropriateness of named parameters is a subjective matter.

Note: A final point should be made about named parameters: their syntax is somewhat of an inconsistency in User Language. In most contexts where user-written expressions are allowed, an equal sign is treated as a comparison operator.

For example, in the following statement the variables %value and %crn are compared, and, if equal, the global CRN is set to 1, otherwise it is set to 0:

$setg('CRN', %value=%crn)

If %value or %crn were not defined, this statement would generate a compilation error.

Given this syntactic structure, one might expect that a named argument in a method invocation would be interpreted as a comparison of a field name with a value. For example, one might expect the argument to the New method to be the result of a comparison between field Host and the variable %target:

%socket = new(host=%target)

The reality is that the use of the equal sign is almost always limited to conditions in an If statement, so one probably wouldn't expect to find a comparison in the above context. Object-oriented SOUL takes one step further by interpreting any single token followed by an equal sign in a method parameter as a named argument, whether or not what is to the left of the equal sign is a valid parameter name. For example, the following will produce a compilation error, even if %input is a defined variable, and even though it is not a valid parameter name:

%object:doIfTrue(%input = 1)

In the odd cases where one really wants to pass the result of an equality comparison to a method, the alternative character comparison operator can be used:

%object:doIfTrue(%input eq 1)

or, the comparison can be wrapped in an extra parenthesis:

%object:doIfTrue((%input eq 1))

Parameter description guidelines

A method declaration contains a description of the method's parameters. A parameter description includes the parameter name, type, and attributes (Optional, AllowNull, and so on). The names should be descriptive, as this makes it easier for users of a method to understand what the method does by looking at its declaration. For example, the following method declaration

subroutine fill(%petrol is float, %oil is float)

is much more helpful than this declaration:

subroutine fill(%x is float, %y is float)

With the availability of named parameters, the use of meaningful parameter names becomes even more important, because the parameter names will actually appear in the method invocations:

%multipla:fill(oil=0.5)

Mapping parameter names in declarations to names within methods

It is possible that the ideal names for method declarations are not the ideal names for the parameters within the actual methods themselves. For example, one might have a standard that all parameters should start with parm. in method code, so that it is easy to tell which variables are parameters. If this standard is extended to the names on the declaration, the result is ugly and redundant parameter names in declarations and method invocations:

%multipla:fill(parm.oil=0.5)

The InternalNames block is provided to allow mapping of parameter names on declarations to another name to be used within the method, making it possible to use the optimal names on method declarations and within methods, even if the two names are not the same.

The syntax of the InternalNames block is:

InternalNames newName [Is] parameterName ... End InternalNames

Where:

newNameThe name to be assigned to that parameter for internal use.
parameterNameThe name of the parameter as it appears in the method declaration.

The InternalNames statement must be the first statement after a method definition header, which means that for properties it must appear before the Get and Set blocks.

For properties, the InternalNames block changes the names of parameters for both the Get and Set methods. InternalNames can be used to rename implicit parameters, such as:

  • %this, which is used to reference the method object.
  • The input value parameter for properties, which has the same name as the property (preceded by a percent).

In the following example, the input variables %petrol and %oil are mapped to the names %parm.petrol and %parm.oil, respectively:

subroutine add(%petrol is float nameRequired optional, - %oil is float nameRequired optional) internalNames %parm.petrol is %petrol %parm.oil is %oil end internalNames ... %petrol = %petrol + %parm.petrol %oil = %oil + %parm.oil

This example illustrates how if petrol and oil were private class variables, the InternalNames block makes it possible to have parameters in a method declaration with the same name, but to avoid naming conflict or confusion between the private class variables and the parameter names.

Stringing method invocations together

Often, a method will return an object, that is, an instance of a class. In such cases, it is possible to invoke another method against the result object by stringing together method invocations.

In the following example, the method object for the Slap subroutine is the object returned by the Call function:

class comic public function call(%name is string len 32) is object comic subroutine slap ... end public ... end class ... %moe is object comic ... %moe:call('Curly):slap

The same is true for stringing a method invocation to an object variable in a class:

class comic public variable brother is object comic subroutine slap ... end public ... end class ... %moe is object comic ... %moe:brother:slap

This stringing of method/variable names can be extended indefinitely and is one of the big advantages of object-oriented syntax over standard procedure syntax. Procedural syntax accomplishes the same economy using nesting:

call $slap($brother(%moe))

With procedural syntax, the evaluation is from the inside out and should be read that way. Evaluation of strung-together class members is left to right, and so it can be read in that more natural order. If there is more than one input object to a method, object oriented syntax must also resort to nesting, but this is relatively rare and still requires one fewer nested object than procedural syntax.

See also