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.
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 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:
self ?args?
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.
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
}
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=0Command create foo -cmd {} {
set rgb {65535 0 0}
foreach {r g b} [get rgb] {}
return "r=$r, g=$g, b=$b"
}
foo
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:
- Firstly, variables can be accessed using self and by calling a method that
accesses a variable (such as get or set).
- Secondly, we can access an object's variable using $-substitution. This only
works if there is no same-named local variable that overshadows the instance
variable of the object.
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
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.
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
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
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).
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 ?levelsUp?
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.
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
callstack level returns the current level of the callstack as an
integer value.
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 ?levelsUp?
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.
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 ?levelsUp?
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.
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
callstack trace is a convenience method to print out a callstack
trace like those printed during error messages.
In a method, you can invoke the following to print the current callstack
trace to the standard output:
puts [callstack trace]
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
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.
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 -- -*