Frag Logo
  Frag Home | Frag SF Project   index | contents | previous | next

Methods

Methods

Defining Methods

Methods are defined - similar to variable slots - in the method table of their object. Thus they are also fully dynamic. Each object can have methods, but these are only applied in the context of its instances, when it acts as a class.

A method is simply defined by invoking the method operation and providing it with the method name, the parameter list, and the body of the method, such as:
ConnectionLogger method writeMsg {channel msg} {
      puts $channel $msg
}

Methods are defined in the context of a class, and they can only be applied for the instances of a class.

The "args" Parameter

In the typical case the formal parameter of the method and the actual parameters of the invocation must have the same number. The special argument name args contains a list with the rest of the arguments. It must be the last argument in the parameter list. From 0 to n arguments can then be handed to the method invocation of an args method. Consider the following example:
Command create sum -cmd {args} {
    set result 0
    foreach n $args {
        set result ($result + $n)
    }
    return $result
}
Now, for instance, the following invocation with three arguments returns 9:
sum 1 3 5
The following invocation with five arguments returns 60:
sum 1 3 5 45 6
The following invocation with no arguments returns 0:
sum

The "self" Command

The command self can be used to refer to the current object. self is especially used to call methods of the current object. For instance, we can use self to invoke writeMsg (another method of ConnectionLogger):
ConnectionLogger method close {} {
    self writeMsg stdout "Closed connection: [self]"
    ...
}
Details for self are are explained below:

Syntax

self ?args?

Description

Invoked without arguments, self returns the object name of the currently executing object. This command is a short-cut for callstack self (see Section callstack self). If it is called outside of an object's method (i.e. from the global callframe) an error is thrown.

Invoked with arguments, self executes the args invocation on the self object. That is, the first element in args is treated as a method name, and this method is invoked on self with the following args elements as arguments.

See also Section The "self" Command.

Examples

The following code contains a method on Object that can be used to print the name of an object using puts. The name of the instance o is printed as an example for using this method.
Object method printObjectName {} {
    puts "Object Name = [self]"
}
Object create o
o printObjectName
The following method uses self to set a variable on an instance. set in the self-arguments is treated as a method name.
o method invoke-set {} {
    self set r 1
}

The "return" command

Methods return per default, the result of the last invocation in the method execution. return is called in a method to stop the method, and return with the given returnValue, which is handed as method result to the invocation of the method. It has the syntax:
return ?returnValue?
For example, the following method returns the value: r=65535, g=0, b=0
Command create foo -cmd {} {
    set rgb {65535 0 0}
    foreach {r g b} [get rgb] {}
    return "r=$r, g=$g, b=$b"
}
foo

Variable Access in Methods

As seen above, local variables can be read using $ or get, and written using set. There are two variants to access object variables in methods:

The following method calculates the sum of two variables. It reads the variable a from the instance scope, the value of b is obtained with get (i.e. it demonstrates both variants of accessing an object's variables from a method). Note that both variables must be set on the instance of X, otherwise a runtime error is raised.
X method add {} {
      return ($a + [self get b])
}
# create an instance, with the variables
X create x -set a 1 -set b 2
# call the method of X
x add

Constructor

There is one special method in Frag, the constructor init, which will be automatically called upon creation of an object. Here is a simple example:
X method init args {
    puts "new instance of X"
    next
}

The above example constructor prints new instance of X to the standard output during the creation of all instances of X. Note that we use next here to pass the message to superclasses (see below in Section Class Hierarchy and Class Path Linearizing). Without the next invocation, the superclasses' constructors would be overwritten by this constructor.

Dynamic Methods

Methods are fully dynamic and can be redefined at any time by simply overwriting the method definition. We can also dynamically add new methods. For instance, we can change the above provided method init to log before and after creation of the instance:
X method init args {
    puts "before creation of new instance of X"
    next
    puts "after creation of new instance of X"
}
Sometimes we want to delete a method without providing a substitute. This can be done with the method deleteMethod. Sometimes, we just want to rename a method: renameMethod can be used to give a method another method name. For instance, we can delete the constructor of X, and rename add:
X deleteMethod init
X renameMethod add addAB

Configure Arguments

In the constructor, args is used for variable length argument lists because argument lists of init often vary. This is because upon creation all argument appended with - (so called configure arguments) are sent as messages to the object. Consider for instance the following invocation:
X create anObject -set r 12 -aMethod

This invocation means that during creation the method set is called with two arguments and the method aMethod is called with no arguments. This is a handy format for passing configuration options to objects.

A special configure argument is -noInit. When it is handed to a create invocation this means that the constructor is not invoked during creation:
X create anObject -noInit

Reflection on Methods

At runtime you can find out about the methods of an object using the getMethods method. The following code defines an object with 3 methods. Next it prints the list of all methods and then all methods starting with x. The result is: x2 x1 y1 and x2 x1.
Object create O
O method x1 {} {;}
O method x2 {} {;}
O method y1 {} {;}
puts [O getMethods] 
puts [O getMethods x*]

getArgs and getBody can be used to introspect the definition of a method. The following example returns the body and args of a method:
Object create O
O method m1 {a b c} {puts "$a $b $c"}
puts "args of m1: [O getArgs m1]; body of m1: [O getBody m1]"
For more on the introspection options, see Section Object: Method Reference (especially the getter-methods of Object).

Callstack Information

In Frag all invocations are handled on a callstack. This callstack is fully accessible from within the language. We have already seen one example of callstack information, self, used to obtain the currently executing object.

All callstack information is provided using the object callstack. self is actually an alias for callstack self. callstack self returns the top-level object on the callstack. With the callstack information options, objects, methods, and classes at any level of the callstack can be queried. In typical programming situations, the top-level (level 0) and the calling level (level 1) that represent the current and calling scope are important.

For instance, the following method prints out the name of the object and method that has invoked it:
X method callerPrinter {} {
      puts "Invoked by [callstack callingObject]->[callstack callingMethod]"
}
For more on callstack information, see the following method reference for the callstack command.

callstack class

Syntax

callstack class ?levelsUp?

Description

callstack class returns the object name of the class on which the currently executing method is defined. If this method is called outside of an object's method (i.e. from the global callframe) an error is thrown.

callstack class takes an optional argument levelsUp. It returns the class name executing at levelsUp levels up the callstack.

Example

The following code returns the name of the class on which the executed method is defined: C. If the code in the method would be callstack class 1, the name of the class of the invoking method would be returned (or an error message, if o class was called from the global callframe).
Object create C
C method class {} {
    return [callstack class] 
}
C create o
o class

callstack level

Syntax

callstack level

Description

callstack level returns the current level of the callstack as an integer value.

Example

At the global level, the callstack level is 0. Hence, if you would define a method like the following and call it from the global level using the invocation o level, this returns the list 1 0.
Object create o -classes o
o method level {} {
    return [list build [callstack level]
        [eval -uplevel {callstack level}]]
}

callstack method

Syntax

callstack method ?levelsUp?

Description

callstack method returns the method name of the currently executing method. If this method is called outside of an object's method (i.e. from the global callframe) an error is thrown.

callstack method takes an optional argument levelsUp. It must be a positive integer describing a level on the callstack. The result is the method name of the method executing at levelsUp levels up the callstack.

Example

The following code returns the method name of the executing method: methodX. If the code in the method would be callstack method 1, the name of the invoking method would be returned (or an error message, if o methodX was called from the global callframe). In the example below, where a test method is used to call methodX, the result is: methodX testMethod testMethod.
Object create o -classes o
o method methodX {} {
    return [list build [callstack method]
        [eval -uplevel {callstack method}]
        [callstack method 1]]
}
o method testMethod {} {
    self methodX
}
o testMethod

callstack self

Syntax

callstack self ?levelsUp?

Description

callstack self returns the object name of the currently executing object. The result is identical to (and callstack self is internally invoked by) the short-cut self (see Section The "self" Command). If it is called outside of an object's method (i.e. from the global callframe) an error is thrown.

callstack self takes an optional argument levelsUp. It must be a positive integer describing a level on the callstack. The result is the object name that is executing at levelsUp levels up the callstack.

Example

The following code returns the name of the object o, which is the current self object. If the code in the method would be callstack self 1, the name of the object which is invoking o self would be returned (or an error message, if o self was called from the global callframe).
Object create o -classes o
o method self {} {
    return [callstack self]
}
o self

The following code results in "p" as p is the object from which the method on o was called, which invokes callstack self 1 (i.e. we are introspecting for the "calling object").
Object create o -classes o
o method callingObject {} {
    return [callstack self 1]
}
Object create p -classes p
p method o-callingObject {} {
    return [o callingObject]
}
p o-callingObject

callstack trace

Syntax

callstack trace

Description

callstack trace is a convenience method to print out a callstack trace like those printed during error messages.

Example

In a method, you can invoke the following to print the current callstack trace to the standard output:
puts [callstack trace]

Dispatcher Methods: * and ?

Method "?"

Sometimes Frag cannot resolve a method invocation within the class hierarchy. The standard behavior is to raise a runtime error, but in some cases a user-defined behavior should be provided.

Such tasks can be handled by a special ? dispatcher method that might be defined for each class. When a method is not found on the class hierarchy, Frag does not raise an error itself, but invokes the method ?. A ? method overrides the standard behavior of Frag to raise an error, if a method is not found.

Subclasses can override ? methods and thus handle messages, not defined in Frag, arbitrarily. For instance consider, we want to send all method invocations that are not found for a client proxy object to a remote object specified by an URL:
Object create ClientProxy -defaults {requestHandler "" URL ""}
ClientProxy method ? args {
    eval $requestHandler invokeViaURL $URL $args
}

If we create a client proxy object, we can still use all predefined methods, such as set. But if a method is not defined, such as these in the example below, the ? dispatcher method is invoked:
ClientProxy create cp
cp set requestHandler "abc"
cp set URL "http://xay.com"
cp these args will be send by the ? dispatcher

Method "*"

The method * is similar to ?, but it is not invoked after methods are searched on an object, but instead it is invoked before the method search. That is, the * dispatcher intercepts every invocation to an object, before it reaches the actual object.

Commands are Frag objects which just implement one *-dispatcher method (see Section Commands) and which have themself as a class, so that invocations can be dispatched just by invoking the object name. Commands can have arbitrary numbers of arguments, which are handed to their *-dispatcher method upon invocation. Typical examples are set, get, math etc.

Using *-dispatchers developers can define their own commands. For instance, a square command can be implemented as follows:
Object create square
square method * {x} {
    return ($x * $x)
}
square classes square

puts "Square of 4: [square 4]"

Using * many kinds of mixins and interceptors can be easily implemented. For instance, consider the classic "logger" example used in many AOP introductions. The following code defines a generic logger, which logs every invocation before and after the invocation to the standard output:
Object create Logger
Logger method * args {
    puts "LOGGED BEFORE: [self] $args"
    set resultOfInvocation [next]
    puts "LOGGED AFTER: [self] $args -- Result = $resultOfInvocation"
    return $resultOfInvocation
}
The next invocation passes the invocation on generically. Hence, we can use the Logger as an extension for all kinds of objects and classes. For instance, we can attach the Logger as a mixin to a class using Frag's mixins feature. This means that all invocations to all instances of that class get logged:
Object create CL
CL create cl1
CL create cl2
# add the Logger to CL
CL mixins Logger
# now everything gets logged
cl1 set x 1
cl2 set y 2
If we would add Logger as a mixin to Object, all invocations in Frag would get logged, because Object is the superclass of all Frag classes. Such mixins hence provide a very powerful debugging feature.

See the Document Object-based and class-based composition of transitive mixins for details on dynamic mixin concepts.

Bypassing Dispatchers

Dispatchers sometimes need to be bypassed. Consider you want to receive the error message of an invocation not being found hidden by the ? dispatcher, or you want to reach an object behind a * dispatcher. This can be done by adding the options -? and -* as the first argument in an invocation.

For instance, if you want to unname the square method, this can be done by using -*:
square2 -* unname

We can send a similar method call to predefined commands as well - e.g., to tailor the language:
while -* unname
This removes the while command's name from the interpreter, and causes the command to be garbage collected.

In seldom cases, it might be possible that -? or -* need to be passed as the first argument to an invocation, and are not intended for bypassing the dispatchers. Consider for instance, we want to print the string "-*" to the standard output. The following code:
puts -*
would yield an error, because the -*-argument bypasses the dispatcher, and then a method argument to be invoked on the object puts is missing. We use -- to tell the interpreter to ignore following -*, -?, and -- arguments. Hence the following code prints the string "-*":
puts -- -*

  index | contents | previous | next