Method variables

From m204wiki
Revision as of 23:28, 27 September 2010 by Alex (talk | contribs)
Jump to navigation Jump to search

Summary

As of Sirius Mods version 7.3, Janus SOAP User Language Interface supports method variables. Method variables are variables that reference methods. They can be used to invoke varying methods with a particular, unchanging invocation. For example, to conditionally invoke the system intrinsic Right, Left, or Center method, you can do something like the following:

%justify is function (string):justify(%amount is float) -
   is longstring
...
if %x then
%justify = right
elseif %y then
%justify = centre
else
%justify = left
end if
...
printtext {'Whatever':%justify(10)}
printtext {%something:%justify(10)}

As shown in the preceding example, to use a method variable you first declare it, then assign a method to it, then invoke it. The declaration specifies the type of method to which the method variable may refer. Functions and Subroutines are the two type options. A Function variable is required if the method (or class variable) you want to assign to the variable returns a value (this is always true for class variables). A Subroutine variable is used if the method to be assigned does not return a value or is Callable.

The method you assign to a method variable can be a system method, a User Language method, (instance method, enhancement method, shared method, or local method), or a class variable. Invoking the method using the method variable employs the same syntax as invoking the method directly, except that the method variable is used.

The following example shows assignment from a user class method, class variables, and a local method:

b
 
class junk
   public
      variable    a is float
      variable    b is string len 32
      function    c is float
   end public
   function    c is float
      return %this:a:toPower(3)
   end function
end class
 
local function (junk):aTimesC is float
   return %this:a * %this:c
end function
 
%rubbish is object junk
%rfunc   is function (junk):whatever is float
 
%rubbish = new
%rubbish:a = 3
%rubbish:b = 'Holy cow!'
 
%rfunc = a
printText {~} = {%rubbish:%rfunc:right(20, pad='*')}
%rfunc = b
printText {~} = {%rubbish:%rfunc}
%rfunc = c
printText {~} = {%rubbish:%rfunc}
%rfunc = aTimesC
printText {~} = {%rubbish:%rfunc}

end

The result is:

%rubbish:%rfunc:right(20, pad='*') = *******************3
%rubbish:%rfunc = Holy cow!
%rubbish:%rfunc = 27
%rubbish:%rfunc = 81

Note: the second line of the result contains string output. Because of loose datatyping for intrinsics, a method variable declared as Float can return a String (or Unicode) value, if the method assigned to it returns such a value.

Notice also that class Variables are used exactly like functions in the examples above.

Function or Subroutine variables may not be passed between requests; they are always nullified between requests. Function or Subroutine variables may not appear inside of global objects.

Declaring a method variable

The syntax for the declaration of a method variable is shown below:

%var [Is] (methodTemplate) | methodTemplate -
          [methvarQualifiers]

Where:

%var
The name of the method variable, which can be any name that follows the rules for User Language %variables.
methodTemplate
The function or subroutine declaration template.
methvarQualifiers
Qualifiers for the variable being declared, such as Common or Shared. If there are any method variable qualifiers, methodTemplate must be enclosed in parentheses. For example if a method variable is Shared, that qualifier must be specified as in:
%x is (function (stringlist):stupid is float) shared

Method template in method variable declaration

A method declaration template is used to declare the type of a method variable and is essentially the same as a normal method declaration, consisting of the method name followed by the method description.

methodType [(class):]methodName[(parms)] -
           [Is returnType] [<methodQualifiers>]
methodType
The type of method that is to be represented by the method variable. The options are Function and Subroutine. Function is required if the method (or class variable) returns a value. Subroutine is used if the method does not return a value or is Callable. Strictly speaking, Function and Subroutine are the classes of the method object that is is referenced by the method variable. There are currently no methods in the Function or Subroutine class.
class
Identifies the class of objects to which the method applies.
methodName
The method name, which can be any name that follows the rules for User Language %variables, is preceded by its class as necessary, and is followed by its parameters, if any.
parms
The parameters that are used in invoking the method variable.
returnType
The datatype of values returned by the methods assigned to the method variable. This is only allowed for Function class method variables.
methodQualifiers
Details of method operation. Since the method operation details tend to come from the method to which the variable is set, methodQualifiers is rarely used.

Method class in method variable method template

Two method variables that are identically declared except for their class specifications are not equivalent. That is, given these declarations:

%foo is function (stringlist):hash is float
%bar is function (stringlist):hash is float
%junk is function (xmlDoc):hash is float

The following is valid:

%foo = %bar

But the following is not valid

%foo = %junk

The class designation supplies context for any method assigned to the method variable. Given the declarations in the previous item, the following assignment implies the Count method in the Stringlist class:

%foo = count

Count above references the Stringlist Count method unless you had created a local Stringlist enhancement method called Count, in which case method %foo references the local Count method.

Omitting a class specification implies that the method referred to by the method variable does not operate on an instance of an object variable, that is, it is a shared method. Such a method variable may be assigned to a conforming shared method in any class.

For example, the following declaration specifies a method variable that does not apply to an object instance:

%debug   is subroutine debug

Consequently, valid assignments could take any of the following forms:

%debug = (myclass):sharedSubroutine
%debug = (yourclass):sharedSubroutine
%debug = localMethod
%debug = (object):garbageCollect

Method parameter list and other qualifiers in method variable method template

The method parameter list can contain required and optional, named and unnamed parameters, and their types. The parameter list is followed by a method result datatype (for functions) and then possibly by further qualifiers.

For example, a Calculate method in Myclass that takes a numeric value and returns a numeric value is declared as:

function (myclass):calculate(%x is float) is float

A method variable declared with a return type that is a User Language intrinsic datatype might return another intrinsic type when invoked if you assign a method to it that returns the other type. This is because User Language allows the assignment from one intrinsic type to another.

Most method qualifiers such as AllowNullObject or Implements are derived from the method assigned to a method variable and make no sense on the method template in a method variable declaration. In fact, there are currently no method qualifiers other than the return datatype that are allowed in the method template in a method variable declaration. This includes Throws clauses though you may assign a method that throws exceptions to a method variable and catch exceptions thrown by that method.

Method variable declaration example

In the following example, the %stooge Function variable is declared for the Mayhem class:

class mayhem
   public
      variable moe    is float
      property larry  is float
      function curly  is float
   end public
end class
 
%stooge is function (mayhem):Number is float

In this declaration Function is used for the object declaration, indicating that the method returns a value. The name Number is actually nothing but a placeholder, and the above declaration is considered identical to:

%stooge is function (mayhem):Placehold is float

Some placeholder is needed in the Function name position to make the declaration read properly, and you may want to put a meaningful name there to suggest the intent of the method. But whether the name is specified as Number or as Placehold, the following assignments are all valid:

%stooge = moe
%stooge = larry
%stooge = curly

The method values to which the method variable in this example refers are in fact class Variables, which fit the %stooge declaration: they are function-like, operating on a Mayhem object and returning a Float.

If the method in the declaration has parameters, as in:

%stooge is function (mayhem):meaningless(%x is float) is float

The parameter names do not get used, so the above declaration is equivalent to this one:

%stooge is function (mayhem):meaningful(%y is float) is float

Thus, if two method variables contain references to methods on a Mayhem object that take a float input parameter and return a float, they can be assigned to each other, as in the following:

%stooge is function (mayhem):meaningless(%x is float) is float
%stud is function (mayhem):meaningful(%y is float)is float
 ...
%stooge = %stud

For NameAllowed or NameRequired parameters, the parameter names are meaningful in the method object declarations.

Assigning to a method variable

You assign a method value to a method variable using the following syntax:

%var = [(+classname)]method

Where:

%var
The name of the method variable.
classname
The name of the class to which the method variable applies. This name is necesary if the method is a shared method or an enhancement method; otherwise, the method is assumed to be a member of the class specified in the method variable declaration. The plus sign is necessary for an enhancement method.
method
The name of the method to which the method variable will refer or an anonymous function. A method name can be either a User Language method (instance method, enhancement method, shared method, or local method), a class member (Variable or read-only Property), or a method variable that fits the #method template in method variable declaration.

The source of an assignment to a method variable can even be the invocation of a function that returns a method value that fits the method description. Of course, it can also be another, compatible method variable.

You may assign a method that takes no parameters to a method variable that is declared as referring to a method with a parameter. For example, if function variable %goo is declared as:

%whynot  is function (silly):whatever(%x is float) is float

The following assignment is valid if Z is a variable in class Silly:

%whynot = Z

In such a case, the parameter, %x, in the method variable declaration is ignored when the method variable is invoked. For example, if %whynot was set to Silly class variable Z, the 22 in the following invocation of %whynot would be ignored.

printText {~} = {%sillyVar:%whynot(22)}

This is allowed because methods are allowed to ignore a parameter value. Similarly, say a function in class Silly is defined as:

function stupid(%whatever is float,         -
         %z is longstring optional) is longstring

Then Stupid may also be validly assigned to %whynot:

%whynot = stupid

And whenever Stupid is invoked via %whynot, the optional parameter %z will not be present.

Method variables follow the standard rules for NameAllowed and NameRequired parameters. If a method variable declaration specifies a method with one of these types of parameter, you may not assign to the variable a method that does not have the same parameter name type.

A method variable may be assigned to an enhancement method. The following statement assigns to %whynot the float enhancement method in class Util called BigOnes which returns a float object:

%whynot = (+util)bigOnes

If a method variable is declared as applying to a base class, you may not assign an extension class method to it. The method variable must be applicable to all objects of the base class, and setting it to a method of a specific extension class would violate this principle.

As of Sirius Mods version 7.6, you may assign an overridable method to a method variable. For versions prior to 7.6, such an assignment is not allowed.

Method variables may be assigned to the special value This, which is an identity method that simply returns its method object.

For example, given the following code fragment:

%justify is function (string):justify(%amount is float) is longstring
 ...
if %x then
   %justify = right
elseif %y then
   %justify = centre
elseif %z
   %justify = left
else
   %justify = this
end if
...
printtext {'Whatever':%justify(10)}
printtext {%something:%justify(10)}

If the Else logical path is followed, the PrintText statement will simply print the string to which it is applied, without justification or padding.

The This value is especially useful for sorting and finding maximum and minimum values in collections of intrinsic objects. This can also be used as an anonymous function where the anonymous function specifies processing to be applied to the method object.

Invoking a method variable

The syntax for invoking a method via a method variable that applies to objects of a class is:

objectVar:%methodVar[:moreMethods]

The syntax for invoking a method via a method variable that doesn't apply to objects of a class is:

%(Local):%methodVar[:moreMethods]

Where:

objectVar
The object variable to which the method variable, %methodVar, is applied.
%methodVar
The name of the method variable.
moreMethods
Additional methods that may be strung together, that is, applied to the result of the %var method. This is no different from stringing a method to the result of a non-variable method.

Method variable invocation examples

In the following example, method variable %foo has this declaration:

%foo is function (stringlist):subset is object stringlist

This declaration indicates that %foo must contain a reference to a method that operates on a Stringlist object and returns a Stringlist object. The following local function is defined in the same scope as %foo, after which the function is assigned to %foo:

local function (stringlist):everyOther is object stringlist
   %outlist is object stringlist
   %i       is float
   %outlist = new
   for %i from 1 to %this:count by 2
      %outlist:add(%this(%i))
   end for
   return %outlist
end function
 ...
%foo = everyOther

To invoke the EveryOther method against Stringlist variable %list, you apply method variable %foo:

%list = %list:%foo

To invoke the EveryOther method against %list and then print the result, you use:

%list:%foo:print

Stringing methods together can be taken further. To invoke EveryOther on %list, then apply EveryOther to the result of that, and finally print the output of those operations:

%list:%foo:%foo:print

In the following example, a method variable that is set to a shared class variable is invoked using %(local), the syntax for invoking a local shared method. The method variable is not applied to a variable because the method, itself, doesn't apply to objects of any class.

begin

class Reply

public shared
   variable  validate  is float  initial(1)
end public shared

end class Reply
 
%rc        is float
%rcMeth    is function rcReturner is float
 
%rcMeth = (Reply):validate
 
%rc = %(local):%rcmeth
if %rc then
   printText Error
else
   printText Success
end if

end

This request prints the word "Error".

Anonymous functions

An anonymous function, as the name suggests, is a method that has no name. Because these functions have no name, they must typically be defined in the context in which they are actually used. Hopefully, the meaning of this statement is made clear below.

Support for anonymous functions was added in Sirius Mods version 7.8.

Consider the case where a method variable is defined as:

%transform is function (string):transform is longstring

So, %transform can refer to a method that takes a string input and produces a string output. So, one can set %transform to the system Reverse function:

%transform = reverse

And then invoke the Reverse function via the method variable:

printText {~} = {'We are the worms':%transform}

which produces

'We are the worms':%transform = smrow eht era eW

But, suppose you wanted %transform to be set to a method that returns the rightmost three characters of the method object string. Intuitively, it would seem that you should be able to do:

%transform = right(3)

But, before Sirius Mods 7.8, this was not allowed. This is because there is no method called Right(3), only Right. And, one couldn't do:

%transform = right

because Right requires a parameter, but the method variable template for %transform has no parameters. And, even if the above were allowed, it's still missing the important bit of information about how many of the rightmost bytes %transform should return.

In Sirius Mods 7.8 and later

%transform = right(3)

is allowed though it's worth understanding exactly what it means. The compiler sees right(3) and "knows" that it can't be a method invocation or variable reference because the right does not begin begin with a percent sign. If the compiler saw:

%transform = %right(3)

this might indicate a reference to an arraylist called %right or, perhaps, a local function called Right that, hopefully, returns a method variable. But, it most definitely would not be interpreted as having anything to do with the system Right method. But, in

%transform = right(3)

the compiler knows you must be refering to a function name and so assume that that function is intended to be part of what's literally being assigned to the method variable. When it sees the open parenthesis after the right it decides that what's inside the parentheses are intended to be arguments to be passed in the specified invocation of the method.

Essentially, it's as if the compiler built a local method under the covers that looked like:

function (string):right3 is longstring
   return %this:right(3)
end functions

and then assigned that local function to %transform:

%transform = right3

In fact, to get this same functionality before Sirius Mods 7.8, you would have to explicitly define such a local function and assign it to %transform. By implicitly doing this for you, the compiler saves you the trouble of having to come up with a name for the method (hence the term anonymous function) and makes it clear at the point of assignment to %transform what the anonymous function actually does. Without the anonymous function, you'd have to find the definition of Right3 to see what's actually being assigned to %transform.

This is another case where User Language's insistence that variables begin with a percent sign comes in handy as it allows the compiler to distinguish an anonymous function from a variable or method reference.

Anonymous functions are always considered to be exposed, that is, variables in the containing context are always visible to the anonymous method. To illustrate this, consider:

b

%transform is function (string):transform is longstring
%nright    is float

%transform = right(%nright)

%nright = 3
printText {~} = {'We are the worms':%transform}

%nright = 7
printText {~} = {'We are the worms':%transform}

end

which outputs

'We are the worms':%transform = rms
'We are the worms':%transform = e worms

Suppose, however, that you want %transform to return the reverse of the last three characters in a string. One might expect the following would do the trick:

%transform = right(3):reverse

However, for subtle syntactic reasons, this is not allowed.

The This method

To accomplish the above, you must use a special method called This. The This method is unusual in that:

  • It can only be used via method variables
  • The first argument is an arbitrary User language expression that can return an datatype. The datatype returned, though, must be compatible with the return value of the method template for the method variable to which it is assigned.
  • The variable %this is automatically defined in the expression and holds the value of the object to which the method variable is applied.

Given these rules, the This method can be used to set a method variable to a function that takes the reverse of the rightmost three characters of a string:

%transform = this(%this:right(3):reverse)

After the above assignment, doing

printText {~} = {'We are the worms':%transform}

produces

'We are the worms':%transform = smr

The This method can be useful in cases where the anonymous function needed to perform an operation that's not a method. For example:

%transform = this(%this with %this)
printText {~} = {'We are the worms':%transform}

outputs

'We are the worms':%transform = We are the wormsWe are the worms

and

%transform = this(%this:left(1) with %this:right(1))
printText {~} = {'We are the worms':%transform}

outputs

'We are the worms':%transform = Ws

As with other anonymous methods, the This method is exposed so variables in the containing context are available inside the This method so that the following:

b

%transform  is function (string):transform is longstring
%middle     is string len 8 initial('<===>')

%transform = this(%this:left(1) with %middle with %this:right(1))
printText {~} = {'We are the worms':%transform}

end

outputs

'We are the worms':%transform = W<===>s

However, since %this inside the This method refers to the method object during the method variable invocation, a %this variable in the containing context would be "hidden". This could be a problem if a This method is used inside a class instance method or an enhancement method where a %this would be defined. For example, if one had:

local subroutine (string):foo

%transform  is function (string):transform is longstring

%transform = this(%this:left(1) with %this with %this:right(1))
printText {~} = {'We are the worms':%transform}

end subroutine

one might actually mean for the %this between the two Withs in the This function to be the %this for the local subroutine. Unfortunately, that will not be the case — %this will be the method object so the above subroutine would end up outputting:

'We are the worms':%transform = WWe are the wormss

To get around this, one can either assign the method's %this to a different local variable:

local subroutine (string):foo

%fooThis   is longstring

%fooThis = %this
%transform  is function (string):transform is longstring

%transform = this(%this:left(1) with %fooThis with %this:right(1))
printText {~} = {'We are the worms':%transform}

end subroutine

or map the name %this via an InternalNames block:

local subroutine (string):foo

internalNames
   %fooThis is %this
end internalNames

%transform  is function (string):transform is longstring

%transform = this(%this:left(1) with %fooThis with %this:right(1))
printText {~} = {'We are the worms':%transform}

end subroutine

If this subroutine is called with:

%x   is string len 8 initial('++++')
%x:foo

the following would be output:

'We are the worms':%transform = W++++s

The This method without any parameters is equivalent to This(%this), that is, it simply returns the method object at method variable invocation time. As such, the This method can be thought of having a default parameter of %this.

Anonymous methods in collection methods

As with other method literals, anonymous methods can be useful in collection methods that use method variables to control processing. For example, suppose we have class Order, with public float variables price and quantity. And, suppose we had:

%orders  is arraylist of object order

We could, then sort the arraylist in descending order by quantity:

%orders:sort(descending(quantity))

If we wanted to sort in descending order by the product of Price and Quantity, we could do:

%orders:sort(descending(this(%this:price * %this:quantity)))

If we had:

%names   is arraylist of longstring

and wanted to sort %names in ascending order of the second through fifth character (for some crazy reason) we could do:

%names:sort(ascending(substring(2,4)))