The Next Scripting Language (NX) is a highly flexible object oriented -scripting language based on Tcl [Ousterhout 1990]. NX is a successor -of XOTcl 1 [Neumann and Zdun 2000a] and was developed based on 10 -years of experience with XOTcl in projects containing several hundred -thousand lines of code. While XOTcl was the first language designed to -provide language support for design patterns, the focus of the Next -Scripting Framework and NX is on combining this with Language -Oriented Programming. In many respects, NX was designed to ease the -learning of the language for novices (by using a more mainstream -terminology, higher orthogonality of the methods, less predefined -methods), to improve maintainability (remove sources of common errors) -and to encourage developers to write better structured programs (to -provide interfaces) especially for large projects, where many -developers are involved.
The Next Scripting Language is based on the Next Scripting Framework -(NSF) which was developed based on the notion of language oriented -programming. The Next Scripting Frameworks provides C-level support -for defining and hosting multiple object systems in a single Tcl -interpreter. The name of the Next Scripting Framework is derived from -the universal method combinator "next", which was introduced in XOTcl. -The combinator "next" serves as a single instrument for method -combination with filters, per-object and transitive per-class mixin -classes, per-object methods and multiple inheritance.
The definition of NX is fully scripted (e.g. defined in -nx.tcl). The Next Scripting Framework is shipped with three language -definitions, containing NX and XOTcl 2. Most of the existing XOTcl 1 -programs can be used without modification in the Next Scripting -Framework by using XOTcl 2. The Next Scripting Framework requires Tcl -8.5 or newer.
1. NX and its Roots
-Object oriented extensions of Tcl have quite a -long history. Two of the most prominent early Tcl based OO languages -were incr Tcl (abbreviated as itcl) and Object Tcl (OTcl -[Wetherall and Lindblad 1995]). While itcl provides a traditional -C++/Java-like object system, OTcl was following the CLOS approach and -supports a dynamic object system, allowing incremental class and -object extensions and re-classing of objects.
Extended Object Tcl (abbreviated as XOTcl [Neumann and Zdun 2000a]) -is a successor of OTcl and was the first language providing language -support for design patterns. XOTcl extends OTcl by providing namespace -support, adding assertions, dynamic object aggregations, slots and by -introducing per-object and per-class filters and per-object and -per-class mixins.
XOTcl was so far released in more than 30 versions. It is described in -its detail in more than 20 papers and serves as a basis for other -object systems like TclOO [Donal ???]. The scripting language NX and -the Next Scripting Framework [Neumann and Sobernig 2009] extend -the basic ideas of XOTcl by providing support for language-oriented -programming. The the Next Scripting Framework supports multiple -object systems concurrently. Effectively, every object system has -different base classes for creating objects and classes. Therefore, -these object systems can have different interfaces and can -follow different naming conventions for built-in methods. Currently, -the Next Scripting Framework is packaged with three object systems: -NX, XOTcl 2.0, and TclCool (the language introduced by TIP#279).
The primary purpose of this document is to introduce NX to beginners. -We expect some prior knowledge of programming languages, and some -knowledge about Tcl. In the following sections we introduce NX by -examples. In later sections we introduce the more advanced concepts of -the language. Conceptually, most of the addressed concepts are very -similar to XOTcl. Concerning the differences between NX and XOTcl, -please refer to the "Migration Guide for the Next Scripting Language".
2. Introductory Overview Example: Stack
-A classical programming example is the implementation of a stack, which -is most likely familiar to many readers from many introductory -programming courses. A stack is a last-in first-out data structure -which is manipulated via operations like push (add something to the -stack) and pop remove an entry from the stack. These operations are -called methods in the context of object oriented programming -systems. Primary goals of object orientation are encapsulation and -abstraction. Therefore, we define a common unit (a class) that defines -and encapsulates the behavior of a stack and provides methods to a user -of the data structure that abstract from the actual implementation.
2.1. Define a Class "Stack"
-In our first example, we define a class named Stack with the methods -push and pop. When an instance of the stack is created (e.g. a -concrete stack s1) the stack will contain an instance variable named -things, initialized with the an empty list.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - | nx::Class create Stack { - - # - # Stack of Things - # - - :variable things {} - - :public method push {thing} { - set :things [linsert ${:things} 0 $thing] - return $thing - } - - :public method pop {} { - set top [lindex ${:things} 0] - set :things [lrange ${:things} 1 end] - return $top - } -} |
Typically, classes are defined in NX via nx::Class create, followed -by the name of the new class (here: Stack). The definition of the -stack placed between curly braces and contains here just the method -definitions. Methods of the class are defined via :method followed -by the name of the method, an argument list and the body of the -method, consisting of Tcl and NX statements.
When an instance of Stack is created, it will contain an instance -variable named things. If several Stack instances are created, -each of the instances will have their own (same-named but different) -instance variable. The instance variable things is used in our -example as a list for the internal representation of the stack. We -define in a next step the methods to access and modify this list -structure. A user of the stack using the provided methods does not -have to have any knowledge about the name or the structure of the -internal representation (the instance variable things).
The method push receives an argument thing which should be placed -on the stack. Note that we do not have to specify the type of the -element on the stack, so we can push strings as well as numbers or -other kind of things. When an element is pushed, we add this element -as the first element to the list things. We insert the element using -the Tcl command linsert which receives the list as first element, -the position where the element should be added as second and the new -element as third argument. To access the value of the instance -variable we use Tcl’s dollar operator followed by the name. The -names of instance variables are preceded with a colon :. Since the -name contains a non-plain character, Tcl requires us to put braces -around the name. The command linsert and its arguments are placed -between square brackets. This means that the function linsert is called and -a new list is returned, where the new element is inserted at the first -position (index 0) in the list things. The result of the linsert -function is assigned again to the instance variable things, which is -updated this way. Finally the method push returns the pushed thing -using the return statement.
The method pop returns the most recently stacked element and removes -it from the stack. Therefore, it takes the first element from the list -(using the Tcl command lindex), assigns it to the method-scoped -variable top, removes the element from the instance variable -things (by using the Tcl command lrange) and returns the value -popped element top.
This finishes our first implementation of the stack, more enhanced -versions will follow. Note that the methods push and pop are -defined as public; this means that these methods can be -used from all other objects in the system. Therefore, these methods -provide an interface to the stack implementation.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - | #!/bin/env tclsh -package require nx - -nx::Class create Stack { - - # - # Stack of Things - # - .... -} - -Stack create s1 -s1 push a -s1 push b -s1 push c -puts [s1 pop] -puts [s1 pop] -s1 destroy |
Now we want to use the stack. The code snippet in Listing 3 shows how to use the class Stack in a script. -Since NX is based on Tcl, the script will be called with the Tcl shell -tclsh. In the Tcl shell we have to require package nx to use the -Next Scripting Framework and NX. The next lines contain the definition -of the stack as presented before. Of course, it is as well possible to -make the definition of the stack an own package, such we could simple -say package require stack, or to save the definition of a stack -simply in a file and load it via source.
In line 12 we create an instance of the stack, namely the stack object -s1. The object s1 is an instance of Stack and has therefore -access to its methods. The methods like push or pop can be invoked -via a command starting with the object name followed by the -method name. In lines 13-15 we push on the stack the values a, then -b, and c. In line 16 we output the result of the pop method -using the Tcl command puts. We will see on standard output the -value+c+ (the last stacked item). The output of the line 17 is the -value b (the previously stacked item). Finally, in line 18 we -destroy the object. This is not necessary here, but shows the life -cycle of an object. In some respects, destroy is the counterpart of -create from line 12.
Figure 4 shows the actual class and -object structure of the first Stack example. Note that the common -root class is nx::Object that contains methods for all objects. -Since classes are as well objects in NX, nx::Class is a -specialization of nx::Object. nx::Class provides methods for -creating objects, such as the method create which is used to create -objects (and classes as well).
2.2. Define an Object Named "stack"
-The definition of the stack in Listing 2 -follows the traditional object oriented approach, found in -practically every object oriented programming language: Define a class -with some methods, create instances from this class, and use the -methods defined in the class in the instances of the class.
In our next example, we introduce generic objects and object -specific methods. With NX, we can define generic objects, which are -instances of the most generic class nx::Object (sometimes called -"common root class"). nx::Object is predefined and contains a -minimal set of methods applicable to all NX objects.
In our second example, we will define a generic object named stack -and provide methods for this object. The methods defined in our first -example were methods provided by a class for objects. Now we defined -object specific methods, which are methods applicable only to the -object for which they are defined.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - | nx::Object create stack { - - :variable things {} - - :public method push {thing} { - set :things [linsert ${:things} 0 $thing] - return $thing - } - - :public method pop {} { - set top [lindex ${:things} 0] - set :things [lrange ${:things} 1 end] - return $top - } -} |
The example in Listing 5 defines the -object stack in a very similar way as the class Stack. But the -following points are different.
-
-
-
-
-First, we use nx::Object instead of nx::Class to denote - that we want to create a generic object, not a class. -
-
- -
-
-As in the example above, we use :variable to define the - instance variable things for this object (the object stack). -
-
-
The definition for the methods push and pop are the same as -before, but this times these methods are object-specific (in general, -all methods defined for an object are object-specific). In order to use -the stack, we can use directly the object stack in the same way as -we have used the object s1 in Listing 3 -the class diagram for this the object stack.
A reader might wonder when to use a class Stack or rather an object -stack. A big difference is certainly that one can define easily -multiple instances of a class, while the object is actually a -singleton. The concept of the object stack is similar to a module, -providing a certain functionality via a common interface, without -providing the functionality to create multiple instances. The reuse of -methods provided by the class to objects is as well a difference. If -the methods of the class are updated, all instances of the class will -immediately get the modified behavior. However, this does not mean that -the reuse for the methods of stack is not possible. NX allows for -example to copy objects (similar to prototype based languages) or to -reuse methods via e.g. aliases (more about this later).
Note that we use capitalized names for classes and lowercase names for -instances. This is not required and a pure convention making it easier -to understand scripts without much analysis.
2.3. Implementing Features using Mixin Classes
-So far, the definition of the stack methods was pretty minimal. -Suppose, we want to define "safe stacks" that protect e.g. against -stack under-runs (a stack under-run happens, when more pop than -push operations are issued on a stack). Safety checking can be -implemented mostly independent from the implementation details of the -stack (usage of internal data structures). There are as well different -ways of checking the safety. Therefore we say that safety checking is -orthogonal to the stack core implementation.
With NX we can define stack-safety as a separate class using methods -with the same names as the implementations before, and "mix" this -behavior into classes or objects. The implementation of Safety in -stack under-runs and to issue error messages, when this happens.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - | nx::Class create Safety { - - # - # Implement stack safety by defining an additional - # instance variable named "count" that keeps track of - # the number of stacked elements. The methods of - # this class have the same names and argument lists - # as the methods of Stack; these methods "shadow" - # the methods of class Stack. - # - - :variable count 0 - - :public method push {thing} { - incr :count - next - } - - :public method pop {} { - if {${:count} == 0} then { error "Stack empty!" } - incr :count -1 - next - } -} |
Note that all the methods of the class Safety end with next. -This command is a primitive command of NX, which calls the -same-named method with the same argument list as the current -invocation.
Assume we save the definition of the class Stack in a file named -Stack.tcl and the definition of the class Safety in a file named -Safety.tcl in the current directory. When we load the classes -Stack and Safety into the same script (see the terminal dialog in -e.g. a certain stack s2 as a safe stack, while all other stacks -(such as s1) might be still "unsafe". This can be achieved via the -option -mixin at the object creation time (see line 9 in -option -mixin mixes the class Safety into the new instance s2.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - | % package require nx -2.0 -% source Stack.tcl -::Stack -% source Safety.tcl -::Safety -% Stack create s1 -::s1 -% Stack create s2 -mixin Safety -::s2 -% s2 push a -a -% s2 pop -a -% s2 pop -Stack empty! - -% s1 info precedence -::Stack ::nx::Object - -% s2 info precedence -::Safety ::Stack ::nx::Object |
When the method push of s2 is called, first the method of the -mixin class Safety will be invoked that increments the counter and -continues with next to call the shadowed method, here the method -push of the Stack implementation that actually pushes the item. -The same happens, when s2 pop is invoked, first the method of -Safety is called, then the method of the Stack. When the stack is -empty (the value of count reaches 0), and pop is invoked, the -mixin class Safety generates an error message (raises an exception), -and does not invoke the method of the Stack.
The last two commands in Listing 8 use introspection to query for the objects -s1 and s2 in which order the involved classes are processed. This -order is called the precedence order and is obtained via info -precedence. We see that the mixin class Safety is only in use for -s2, and takes there precedence over Stack. The common root class -nx::Object is for both s1 and s2 the base class.
Note that the class Safety is only mixed into a single object (here -s2), therefore we refer to this case as a per-object mixin. -Figure 9 shows the class -diagram, where the class Safety is used as a per-object mixin for -s2.
The class Safety can be used as well in other ways, such as e.g. for -defining classes for safe stacks <<xmp-class-safestack,
1 - 2 - 3 - 4 - 5 - 6 - 7 - | # -# Create a safe stack class by using Stack and mixin -# Safety -# -nx::Class create SafeStack -superclass Stack -mixin Safety - -SafeStack create s3 |
The difference to the case with the per-object mixin is that now, -Safety is mixed into the definition of SafeStack. Therefore, all -instances of the class SafeStack (here the instance s3) will be -using the safety definitions. -for this definition.
Note that we could use Safety as well as a per-class mixin on -Stack. In this case, all stacks would be safe stacks and we could -not provide a selective feature selection (which might be perfectly -fine).
2.4. Define Different Kinds of Stacks
-The definition of Stack is generic and allows all kind of elements -to be stacked. Suppose, we want to use the generic stack definition, -but a certain stack (say, stack s4) should be a stack for integers -only. This behavior can be achieved by the same means as introduced -already in Listing 5, namely -object-specific methods.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - | Stack create s4 { - - # - # Create a stack with a object-specific method - # to check the type of entries - # - - :public method push {thing:integer} { - next - } -} |
The program snippet in Listing 12 defines an instance s4 of the class -Stack and provides an object specific method for push to implement -an integer stack. The method pull is the same for the integer stack -as for all other stacks, so it will be reused as usual from the class -Stack. The object-specific method push of s4 has a value -constraint in its argument list (thing:integer) that makes sure, -that only integers can be stacked. In case the argument is not an -integer, an exception will be raised. Of course, one could perform the -value constraint checking as well in the body of the method proc by -accepting an generic argument and by performing the test for the value -in the body of the method. In the case, the passed value is an -integer, the push method of Listing 12 calls next, and therefore calls the -shadowed generic definition of push as provided by Stack.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - | nx::Class create IntegerStack -superclass Stack { - - # - # Create a Stack accepting only integers - # - - :public method push {thing:integer} { - next - } -} |
An alternative approach is shown in -Listing 13, where the class -IntegerStack is defined, using the same method definition -as s4, this time on the class level.
2.5. Define Class Specific Methods
-In our previous examples we defined methods provided by classes -(applicable for their instances) and object-specific methods (methods -defined on objects, which are only applicable for these objects). In -this section, we introduce methods that are defined on classes. These -method are only applicable for the class objects. Such methods are -sometimes called class methods or static methods.
In NX classes are objects with certain properties. The classes are -objects providing methods for instance and which are managing the -life-cycles of the objects (we will come to this point in later -sections in more detail). Since classes are objects, it is also -possible to define object-specific methods for the class -objects. However, since :method applied on classes defines methods -for instances, we have to use the method-modifier class to denote -methods to be applied on the class itself. Note that instances do not -inherit class methods. The methods defined on the class object are -actually exactly same as the object-specific methods shown in the -examples above.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - | nx::Class create Stack2 { - - :public class method available_stacks {} { - return [llength [:info instances]] - } - - :variable things {} - - :public method push {thing} { - set :things [linsert ${:things} 0 $thing] - return $thing - } - - :public method pop {} { - set top [lindex ${:things} 0] - set :things [lrange ${:things} 1 end] - return $top - } -} - -Stack2 create s1 -Stack2 create s2 - -puts [Stack2 available_stacks] |
The class Stack2 in Listing 14 consists of the -earlier definition of the class Stack and is extended by the -class-specific method available_stacks, which returns the -current number of instances of the stack. The final command puts -(line 26) prints 2 to the console.
The class diagram in Figure 15 shows the -diagrammatic representation of the class object-specific method -available_stacks. Since every class is a specialization of the -common root class nx::Object, the common root class is often omitted -from the class diagrams, so it was omitted here as well in the diagram.
3. Basic Language Features of NX
-3.1. Variables and Properties
-In general, NX does not need variable declarations. It allows to -create or modify variables on the fly by using for example the Tcl -commands set and unset. Depending on the variable name (or more -precisely, depending on the variable name’s prefix consisting of -colons :) a variable is either local to a method, or it is an -instance variable, or a global variable. The rules are:
-
-
-
-
-A variable without any colon prefix refers typically to a method - scoped variable. Such a variable is created during the invocation - of the method, and it is deleted, when the method ends. In the - example below, the variable a is method scoped. -
-
- -
-
-A variable with a single colon prefix refers to an instance - variable. An instance variable is part of the object; when the - object is destroyed, its instance variables are deleted as well. In the - example below, the variable b is an instance variable. -
-
- -
-
-A variable with two leading colons refers to a global variable. The - lifespan of a globale variable ends when the variable is explicitly - unset or the script terminates. Variables, which are placed in Tcl - namespaces, are also global variables. In the example below, the - variable c is a global variable. -
-
-
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - | nx::Class create Foo { - - :method foo args {...} - # "a" is a method scoped variable - set a 1 - # "b" is an Instance variable - set :b 2 - # "c" is a global variable/namespaced variable - set ::c 3 - } -} |
Listing 16 shows a method foo -of some class Foo referring to differently scoped variables.
3.1.1. Properties: Instance Variables with Accessors
-Generally, there is no need to define or declare instance variables in -NX. In some cases, however, a definition of instance variables is -useful. NX allows us to define instances variables as properties on -classes, which are inherited to subclasses. Furthermore, the -definition of properties can be used the check permissible values for -instance variables or to initialize instance variables from default -values during object initialization.
A property is a definition of an attribute (an instance variable) -with accessors. The property definition might as well carry -value-constraints and a default value.
The class diagram above defines the classes Person and -Student. For both classes, accessor methods are specified with the -same names as the attributes. (Note that we show the accessor methods -only in this example, we omit it in later ones). By defining -properties we can use the name of the attribute as method name to -access the attributes. The listing below shows an implementation of this -conceptual model in NX.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - | # -# Define a class Person with properties "name" -# and "birthday" -# -nx::Class create Person { - :property name:required - :property birthday -} - -# -# Define a class Student as specialization of Person -# with additional properties -# -nx::Class create Student -superclass Person { - :property matnr:required - :property {oncampus:boolean true} -} - -# -# Create instances using object parameters -# for the initialization -# -Person create p1 -name Bob -Student create s1 -name Susan -matnr 4711 - -# Access property value via accessor method -puts "The name of s1 is [s1 name]" |
By defining name and birthday as properties of Person, NX -provides automatically accessor methods with the same name. The -accessors methods (or shortly called "accessors") provide read and -write access to the underlying instance variables. In our example, the -class Person has two methods implied by the property definition: -the method name and the method birthday.
The class Student is defined as a specialization of Person with -two additional properties: matnr and oncampus. The property -matnr is required (it has to be provided, when an instance of this -class is created), and the property oncampus is boolean, and is per -default set to true. Note that the class Student inherits the -properties of Person. So, Student has four properties in total.
The property definitions are also used to providing object -parameters. These are typically non-positional parameters, which are -used during object creation to supply values to the instance -variables. In our listing, we create an instance of Person using the -object parameter name and provide the value of Bob to the instance -variable name. Similarly, we create an instance of Student using -the two object parameters name and matnr. Finally, we use the -accessor method name to obtain the value of the instance variable -name of object s1.
3.1.2. Instance Variables without Accessors
-To define instances variables with default values without accessors -the predefined method variable can be used. Such instance variables -are often used for e.g. keeping the internal state of an object. The -usage of variable is in many respects similar to property. One -difference is, that property uses the same syntax as for method -parameters, whereas variable receives the default value as a separate -argument (similar to the variable command in Tcl). The introductory -Stack example in Listing 2 uses already -the method variable.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - | nx::Class create Base { - :variable x 1 - # ... -} - -nx::Class create Derived -superclass Base { - :variable y 2 - # ... -} - -# Create instance of the class Derived -Derived create d1 - -# Object d1 has instance variables -# x == 1 and y == 2 |
Note that the variable definitions are inherited in the same way as -properties. The example in Listing 19 shows a -class Derived that inherits from Base. When an instance d1 is -created, it will contain the two instance variables x and y.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - | nx::Class create Base2 { - # ... - :method init {} { - set :x 1 - # .... - } -} - -nx::Class create Derived2 -superclass Base2 { - # ... - :method init {} { - set :y 2 - next - # .... - } -} - -# Create instance of the class Derived2 -Derived2 create d2 |
In many other object oriented languages, the instance variables are -initialized solely by the constructor (similar to class Derived2 in -Listing 20). This approach is certainly -also possible in NX. Note that the approach using constructors -requires an explicit method chaining between the constructors and is -less declarative than the approach in NX using property and variable.
3.2. Method Definitions
-The basic building blocks of an object oriented program are object and -classes, which contain named pieces of code, the methods.
Methods are subroutines (pieces of code) associated with objects -and/or classes. A method has a name, receives optionally arguments -during invocation and returns a value.
Plain Tcl provides subroutines, which are not associated with objects -or classes. Tcl distinguishes between +proc+s (scripted subroutines) -and commands (system-languages implemented subroutines).
Methods might have different scopes, defining, on which kind of -objects these methods are applicable to. These are described in more -detail later on. For the time being, we deal here with methods defined -on classes, which are applicable for the instance of these classes.
3.2.1. Scripted Methods
-Since NX is a scripting language, most methods are most likely -scripted methods, in which the method body contains Tcl code.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - | # Define a class -nx::Class create Dog { - - # Define a scripted method for the class - :public method bark {} { - puts "[self] Bark, bark, bark." - } -} - -# Create an instance of the class -Dog create fido - -# The following line prints "::fido Bark, bark, bark." -fido bark |
In the example above we create a class Dog with a scripted method -named bark. The method body defines the code, which is executed when -the method is invoked. In this example, the method bar prints out a -line on the terminal starting with the object name (this is determined -by the built in command self) followed by "Bark, bark, bark.". This -method is defined on a class and applicable to instances of the class -(here the instance fido).
3.2.2. C-implemented Methods
-Not all of the methods usable in NX are scripted methods; many -predefined methods are defined in the underlying system language, -which is typically C. For example, in Listing 21 we -used the method create to create the class Dog and to create the -dog instance fido. These methods are implemented in C in the next -scripting framework.
C-implemented methods are not only provided by the underlying -framework but might be as well defined by application developers. This -is an advanced topic, not covered here. However, application developer -might reuse some generic C code to define their own C-implemented -methods. Such methods are for example accessors, forwarders and -aliases.
An accessor method is in most cases a C-implemented method that -accesses instance variables of an object. A call to an accessor -without arguments uses the accessor as a getter, obtaining the actual -value of the associated variable. A call to an accessor with an -argument uses it as a setter, setting the value of the associated -variable.
Accessors have already been discussed in the section about properties, -in which the accessors were created automatically.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - | nx::Class create Dog { - :public method bark {} { puts "[self] Bark, bark, bark." } - :method init {} { Tail create [self]::tail} -} - -nx::Class create Tail { - :property {length:double 5} - :public method wag {} {return Joy} -} - -# Create an instance of the class -Dog create fido - -# Use the accessor "length" as a getter, to obtain the value -# of a property. The following call returns the length of the -# tail of fido -fido::tail length - -# Use the accessor "length" as a setter, to alter the value -# of a property. The following call changes the length of -# the tail of fido -fido::tail length 10 - -# Proving an invalid values will raise an error -fido::tail length "Hello" |
Listing 22 shows an extended example, where every dog -has a tail. The object tail is created as a subobject of the dog in -the constructor init. The subobject can be accessed by providing the -full name of the subobject fido::tail. The method length is an -C-implemented accessor, that enforces the value constraint (here a -floating point number, since length uses the value constraint -double).
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - | nx::Class create Dog { - :public method bark {} { puts "[self] Bark, bark, bark." } - :method init {} { - Tail create [self]::tail - :public forward wag [self]::tail wag - } -} - -nx::Class create Tail { - :property {length 5} - :public method wag {} {return Joy} -} - -# Create an instance of the class -Dog create fido - -# The invocation of "fido wag" is delegated to "fido::tail wag". -# Therefore, the following method returns "Joy". -fido wag |
Listing 23 again extends the example by adding a -forwarder named wag to the object (e.g. fido). The forwarder -redirects all calls of the form fido wag with arbitrary arguments to -the subobject fido::tail.
A forwarder method is a -C-implemented method that redirects an invocation for a certain method -to either a method of another object or to some other method of the -same object. Forwarding an invocation of a method to some other -object is a means of delegation.
The functionality of the forwarder can just as well be implemented as -a scripted method, but for the most common cases, the forward -implementation is more efficient, and the forward method expresses -the intention of the developer.
The method forwarder has several options to change e.g. the order of -the arguments, or to substitute certain patterns in the argument list -etc. This will be described in later sections.
3.2.3. Method-Aliases
-An alias method is a means to register either an existing method, -or a Tcl proc, or a Tcl command as a method with the provided -name on a class or object.
In some way, the method alias is a restricted form of a forwarder, -though it does not support delegation to different objects or argument -reordering. The advantage of the method alias compared to a forwarder -is that it has close to zero overhead, especially for aliasing -c-implemented methods.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - | nx::Class create Dog { - :public method bark {} { puts "[self] Bark, bark, bark." } - - # Define a public alias for the method "bark" - :public alias warn [:info method handle bark] - # ... -} - -# Create an instance of the class -Dog create fido - -# The following line prints "::fido Bark, bark, bark." -fido warn |
Listing 24 extends the last example by defining an -alias for the method "bark". The example only shows the bare -mechanism. In general, method aliases are very powerful means for -reusing pre-existing functionality. The full object system of NX and -XOTcl2 is built from aliases, reusing functionality provided by the -next scripting framework under different names. Method aliases -are as well a means for implementing traits in NX.
3.3. Method Protection
-All kinds of methods might have different kind of protections in NX. -The call-protection defines from which calling context methods might -be called. The Next Scripting Framework supports as well redefinition -protection for methods.
NX distinguished between public, protected and private methods, -where the default call-protection is "protected".
A public method can be called from every context. A protected -method can only be invoked from the same object. A private method -can only be invoked from methods defined on the same entity -(defined on the same class or on the same object) via the invocation -with the local flag (i.e. ": -local").
All kind of method protections are applicable for all kind of methods, -either scripted or C-implemented.
The distinction between public and protected leads to interfaces for -classes and objects. Public methods are intended for consumers of -these entities. Public methods define the intended ways of providing -methods for external usages (usages, from other objects or -classes). Protected methods are intended for the implementor of the -class or subclasses and not for public usage. The distinction between -protected and public reduces the coupling between consumers and the -implementation, and offers more flexibility to the developer.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - | nx::Class create Foo { - - # Define a public method - :public method foo {} { - # .... - return [:helper] - } - - # Define a protected method - :method helper {} { - return 1 - } -} - -# Create an instance of the class: -Foo create f1 - -# The invocation of the public method "foo" returns 1 -f1 foo - -# The invocation of the protected method "helper" raises an error: -f1 helper |
The example above uses :protected method helper …. We could have -used here as well :method helper …, since the default method -call-protection is already protected.
The method call-protection of private goes one step further and -helps to hide implementation details also for implementors of -subclasses. Private methods are a means for avoiding unanticipated name -clashes. Consider the following example:
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - | nx::Class create Base { - :private method helper {a b} {expr {$a + $b}} - :public method foo {a b} {: -local helper $a $b} -} - -nx::Class create Sub -superclass Base { - :public method bar {a b} {: -local helper $a $b} - :private method helper {a b} {expr {$a * $b}} - :create s1 -} - -s1 foo 3 4 ;# returns 7 -s1 bar 3 4 ;# returns 12 -s1 helper 3 4 ;# raises error: unable to dispatch method helper |
The base class implements a public method foo using the helper -method named helper. The derived class implements a as well a public -method bar, which is also using a helper method named helper. When -an instance s1 is created from the derived class, the method foo -is invoked which uses in turn the private method of the base -class. Therefore, the invocation s1 foo 3 4 returns its sum. If -the local flag had not beed used in helper, s1 would -have tried to call the helper of Sub, which would be incorrect. For -all other purposes, the private methods are "invisible" in all -situations, e.g., when mixins are used, or within the next-path, etc.
By using the local flag for the invocation it is possible to call -only the local definition of the method. If we would call the method -as usual, the resolution order would be the same as usual, starting -with filters, mixins, per-object methods and the full intrinsic class -hierarchy.
3.4. Applicability of Methods
-As defined above, a method is a subroutine defined on an object or -class. This object (or class) contains the method. If the object (or -class) is deleted, the contained methods will be deleted as well.
3.4.1. Instance Methods
-Typically, methods are defined on a class, and the methods defined on the -class are applicable to the instances (direct or indirect) of this -class. These methods are called instance methods.
In the following example method, foo is an instance method defined -on class C.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - | nx::Class create C { - :public method foo {} {return 1} - :create c1 -} - -# Method "foo" is defined on class "C" -# and applicable to the instances of "C" -c1 foo |
There are many programming languages that only allow these types of methods. -However, NX also allows methods to be defined on objects.
3.4.2. Object Methods
-Methods defined on objects are per-object methods. Per-object -methods are only applicable on the object, on which they are defined. -Per-object methods cannot be inherited from other objects.
The following example defines an object specific method bar on the -instance c1 of class C, and as well as the object specific method -baz defined on the object o1. An object-specific method is defined -simply by defining the method on an object.
Note that we can define a per-object method that shadows (redefines) -for this object an intrinsic instance method.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - | nx::Class create C { - :public method foo {} {return 1} - :create c1 { - :public method foo {} {return 2} - :public method bar {} {return 3} - } -} - -# Method "bar" is an object specific method of "c1" -c1 bar - -# object-specific method "foo" returns 2 -c1 foo - -# Method "baz" is an object specific method of "o1" -nx::Object create o1 { - :public method baz {} {return 4} -} -o1 baz |
3.4.3. Class Methods
-A class method is a method defined on a class, which is only -applicable to the class object itself. The class method is a -per-object method of the class object.
In NX, all classes are objects. Classes are in NX special kind of -objects that have e.g. the ability to create instances and to provide -methods for the instances. Classes manage their instances. The general -method set for classes is defined on the meta-classes (more about -this later).
The following example defines a public class method bar on class -C. The class method is specified by using the modifier class in -front of method in the method definition command.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - | nx::Class create C { - # - # Define a class method "bar" and an instance - # method "foo" - # - :public class method bar {} {return 2} - :public method foo {} {return 1} - - # - # Create an instance of the current class - # - :create c1 -} - -# Method "bar" is a class method of class "C" -# therefore applicable on the class object "C" -C bar - -# Method "foo" is an instance method of "C" -# therefore applicable on instance "c1" -c1 foo - -# When trying to invoke the class method on the -# instance, an error will be raised. -c1 bar |
In some other object oriented programming languages, class methods -are called "static methods".
3.5. Ensemble Methods
-NX provides "ensemble methods" as a means to structure the method name -space and to group related methods. Ensemble methods are similar in -concept to Tcl’s ensemble commands.
An ensemble method is a form of a hierarchical method consisting of -a container method and sub-methods. The first argument of the -container method is interpreted as a selector (the sub-method). Every -sub-method can be an container method as well.
Ensemble methods provide a means to group related commands together, -and they are extensible in various ways. It is possible to add -sub-methods at any time to existing ensembles. Furthermore, it is -possible to extend ensemble methods via mixin classes.
The following example defines an ensemble method for string. An -ensemble method is defined when the provide method name contains a -space.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - | nx::Class create C { - - # Define an ensemble method "string" with sub-methods - # "length", "tolower" and "info" - - :public method "string length" {x} {....} - :public method "string tolower" {x} {...} - :public method "string info" {x} {...} - ... - :create c1 -} - -# Invoke the ensemble method -c1 string length "hello world" |
3.6. Method Resolution
-When a method is invoked, the applicable method is searched in the -following order:
In the case, no mixins are involved, first the object is searched for -a per-object method with the given name, and then the class hierarchy -of the object. The method can be defined multiple times on the search -path, so some of these method definitions might be shadowed by the -more specific definitions.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - | nx::Class create C { - :public method foo {} { return "C foo: [next]"} -} - -nx::Class create D -superclass C { - - :public method foo {} { return "D foo: [next]"} - - :create d1 { - :public method foo {} { return "d1 foo: [next]"} - } -} - -# Invoke the method foo -d1 foo -# result: "d1 foo: D foo: C foo: " - -# Query the precedence order from NX via introspection -d1 info precedence -# result: "::D ::C ::nx::Object" |
Consider the example in -foo is invoked on object d1, the per-object method has the highest -precedence and is therefore invoked. The per-object methods shadows -the same-named methods in the class hierarchy, namely the method foo -of class D and the method foo of class C. The shadowed methods -can be still invoked, either via the primitive next or via method -handles (we used already method handles in the section about method -aliases). In the example above, next calls the shadowed method and -add their results to the results of every method. So, the final result -contains parts from d1, D and C. Note, that the topmost next -in method foo of class C shadows no method foo and simply -returns empty (and not an error message).
The introspection method info precedence provides information about -the order, in which classes processed during method resolution.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - | nx::Class create M1 { - :public method foo {} { return "M1 foo: [next]"} -} -nx::Class create M2 { - :public method foo {} { return "M2 foo: [next]"} -} - -# -# "d1" is created based on the definitions of the last example -# -# Add the methods from "M1" as per-object mixin to "d1" -d1 mixin M1 - -# -# Add the methods from "M2" as per-class mixin to class "C" -C mixin M2 - -# Invoke the method foo -d1 foo -# result: "M1 foo: M2 foo: d1 foo: D foo: C foo: " - -# Query the precedence order from NX via introspection -d1 info precedence -# result: "::M1 ::M2 ::D ::C ::nx::Object" |
an extension of the previous example. We define here two additional -classes M1 and M2 which are used as per-object and per-class -mixins. Both classes define the method foo, these methods shadow -the definitions of the intrinsic class hierarchy. Therefore an -invocation of foo on object d1 causes first an invocation of -method in the per-object mixin.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - | # -# "d1" is created based on the definitions of the last two examples, -# the mixins "M1" and "M2" are registered. -# -# Define a public per-object method "bar", which calls the method -# "foo" which various invocation options: -# -d1 public method bar {} { - puts [:foo] - puts [: -local foo] - puts [: -intrinsic foo] - puts [: -system foo] -} - -# Invoke the method "bar" -d1 bar |
In the first line of the body of method bar, the method foo is -called as usual with an implicit receiver, which defaults to the -current object (therefore, the call is equivalent to d1 foo). The -next three calls show how to provide flags that influence the method -resolution. The flags can be provided between the colon and the method -name. These flags are used rather seldomly but can be helpful in some -situations.
The invocation flag -local means that the method has to be resolved -from the same place, where the current method is defined. Since the -current method is defined as a per-object method, foo is resolved as -a per-object method. The effect is that the mixin definitions are -ignored. The invocation flag -local was already introduced int the -section about method protection, where it was used to call private -methods.
The invocation flag -intrinsic means that the method has to be resolved -from the intrinsic definitions, meaning simply without mixins. The -effect is here the same as with the invocation flag -local.
The invocation flag -system means that the method has to be resolved -from basic - typically predefined - classes of the object system. This -can be useful, when script overloads system methods, but still want to -call the shadowed methods from the base classes. In our case, we have -no definitions of foo on the base clases, therefore an error message -is returned.
M1 foo: M2 foo: d1 foo: D foo: C foo: - d1 foo: D foo: C foo: - d1 foo: D foo: C foo: - ::d1: unable to dispatch method 'foo'-
3.7. Parameters
-NX provides a generalized mechanism for passing values to either -methods (we refer to these as method parameters) or to objects -(these are called object parameters). Both kind of parameters -might have different features, such as:
-
-
-
-
-Positional and non-positional parameters -
-
- -
-
-Required and non-required parameters -
-
- -
-
-Default values for parameters -
-
- -
-
-Value-checking for parameters -
-
- -
-
-Multiplicity of parameters -
-
-
TODO: complete list above and provide a short summary of the section
Before we discuss method and object parameters in more detail, we -describe the parameter features in the subsequent sections based on -method parameters.
3.7.1. Positional and Non-Positional Parameters
-If the position of a parameter in the list of formal arguments -(e.g. passed to a function) is significant for its meaning, this is a -positional parameter. If the meaning of the parameter is independent -of its position, this is a non-positional parameter. When we call a -method with positional parameters, the meaning of the parameters (the -association with the argument in the argument list of the method) is -determined by its position. When we call a method with non-positional -parameters, their meaning is determined via a name passed with the -argument during invocation.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - | nx::Object create o1 { - - # - # Method foo has positional parameters: - # - :public method foo {x y} { - puts "x=$x y=$y" - } - - # - # Method bar has non-positional parameters: - # - :public method bar {-x -y} { - puts "x=$x y=$y" - } - - # - # Method baz has non-positional and - # positional parameters: - # - :public method baz {-x -y a} { - puts "x? [info exists x] y? [info exists y] a=$a" - } -} - -# invoke foo (positional parameters) -o1 foo 1 2 - -# invoke bar (non-positional parameters) -o1 bar -y 3 -x 1 -o1 bar -x 1 -y 3 - -# invoke baz (positional and non-positional parameters) -o1 baz -x 1 100 -o1 baz 200 -o1 baz -- -y |
Consider the example in Listing 34. The method -foo has the argument list x y. This means that the first argument -is passed in an invocation like o1 foo 1 2 to x (here, the value -1), and the second argument is passed to y (here the value 2). -Method bar has in contrary just with non-positional arguments. Here -we pass the names of the parameter together with the values. In the -invocation o1 bar -y 3 -x 1 the names of the parameters are prefixed -with a dash ("-"). No matter whether in which order we write the -non-positional parameters in the invocation (see line 30 and 31 in -Listing 34) in both cases the variables x -and y in the body of the method bar get the same values assigned -(x becomes 1, y becomes 3).
It is certainly possible to combine positional and non-positional -arguments. Method baz provides two non-positional parameter (-y -and -y) and one positional parameter (namely a). The invocation in -line 34 passes the value of 1 to x and the value of 100 to a. -There is no value passed to y, therefore value of y will be -undefined in the body of baz, info exists y checks for the -existence of the variable y and returns 0.
The invocation in line 35 passes only a value to the positional -parameter. A more tricky case is in line 36, where we want to pass --y as a value to the positional parameter a. The case is more -tricky since syntactically the argument parser might consider -y as -the name of one of the non-positional parameter. Therefore we use -- -(double dash) to indicate the end of the block of the non-positional -parameters and therefore the value of -y is passed to a.
3.7.2. Optional and Required Parameters
-Per default positional parameters are required, and non-positional -parameters are optional (they can be left out). By using parameter -options, we can as well define positional parameters, which are -optional, and non-positional parameters, which are required.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - | nx::Object create o2 { - - # - # Method foo has one required and one optional - # positional parameter: - # - :public method foo {x:required y:optional} { - puts "x=$x y? [info exists y]" - } - - # - # Method bar has one required and one optional - # non-positional parameter: - # - :public method bar {-x:required -y:optional} { - puts "x=$x y? [info exists y]" - } -} - -# invoke foo (one optional positional parameter is missing) -o2 foo 1 |
The example in Listing 35 defined method foo -with one required and one optional positional parameter. For this -purpose we use the parameter options required and optional. The -parameter options are separated from the parameter name by a colon. If -there are multiple parameter options, these are separated by commas -(we show this in later examples).
The parameter definition x:required for method foo is equivalent -to x without any parameter options (see e.g. previous example), -since positional parameters are per default required. The invocation -in line 21 of Listing 35 will lead to an -undefined variable y in method foo, because no value us passed to -the optional parameter. Note that only trailing positional parameters might be -optional. If we would call method foo of Listing 34 with only one argument, the system would raise an -exception.
Similarly, we define method bar in Listing 35 with one required and one optional non-positional -parameter. The parameter definition -y:optional is equivalent to --y, since non-positional parameter are per default optional. -However, the non-positional parameter -x:required is required. If we -invoke bar without it, the system will raise an exception.
3.7.3. Default Values for Parameters
-Optional parameters might have a default value, which will be used, -when not value is provided for this parameter. Default values can be -specified for positional and non-positional parameters.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - | nx::Object create o3 { - - # - # Positional parameter with default value: - # - :public method foo {x:required {y 101}} { - puts "x=$x y? [info exists y]" - } - - # - # Non-positional parameter with default value: - # - :public method bar {{-x 10} {-y 20}} { - puts "x=$x y? [info exists y]" - } -} - -# use default values -o3 foo -o3 bar |
In order to define a default value, the parameter specification must -be of the form of a 2 element list, where the second argument is the -default value. See for an example in -Listing 36.
3.7.4. Value Constraints
-NX provides value constraints for all kind of parameters. By -specifying value constraints a developer can restrict the permissible -values for a parameter and document the expected values in the source -code. Value checking in NX is conditional, it can be turned on or off -in general or on a per-usage level (more about this later). The same -mechanisms can be used not only for input value checking, but as well -for return value checking (we will address this point as well later).
Built-in Value Constraints
-NX comes with a set of built-in value constraints, which can be -extended on the scripting level. The built-in checkers are either the -native checkers provided directly by the Next Scripting Framework (the -most efficient checkers) or the value checkers provided by Tcl through -string is …. The built-in checkers have as well the advantage that -they can be used also at any time during bootstrap of an object -system, at a time, when e.g. no objects or methods are defined. The -same checkers are used as well for all C-implemented primitives of NX -and the Next Scripting Framework.
Figure 37 shows the built-in -general applicable value checkers available in NX, which can be used -for all method and object parameters. In the next step, we show how to -use these value-checkers for checking permissible values for method -parameters. Then we will show, how to provide more detailed value -constraints.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - | nx::Object create o4 { - - # - # Positional parameter with value constraints: - # - :public method foo {x:integer o:object,optional} { - puts "x=$x o? [info exists o]" - } - - # - # Non-positional parameter with value constraints: - # - :public method bar {{-x:integer 10} {-verbose:boolean false}} { - puts "x=$x y=$y" - } -} - -# The following invocation raises an exception -o4 foo a |
Value contraints are specified as parameter options in the parameter -specifications. The parameter specification x:integer defines x as -a required positional parmeter which value is constraint to an -integer. The parameter specification o:object,optional shows how to -combine multiple parameter options. The parameter o is an optional -positional parameter, its value must be an object (see -Listing 38). Value constraints are -specified exactly the same way for non-positional parameters (see -method bar in Listing 38).
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - | # -# Create classes for Person and Project -# -nx::Class create Person -nx::Class create Project - -nx::Object create o5 { - # - # Parameterized value constraints - # - :public method work { - -person:object,type=Person - -project:object,type=Project - } { - # ... - } -} - -# -# Create a Person and a Project instance -# -Person create gustaf -Project create nx - -# -# Use method with value constraints -# -o5 work -person gustaf -project nx |
The native checkers object, class, metaclass and baseclass can -be further specialized with the parameter option type to restrict -the permissible values to instances of certain classes. We can use for -example the native value constraint object either for testing -whether an argument is some object (without further constraints, as in -Listing 36, method foo), or we can -constrain the value further to some type (direct or indirect instance -of a class). This is shown by method work in -Listing 39 which requires -the parameter -person to be an instance of class Person and the -parameter -project to be an instance of class Project.
Scripted Value Constraints
-The set of predefined value checkers can be extended by application -programs via defining methods following certain conventions. The user -defined value checkers are defined as methods of the class nx::Slot -or of one of its subclasses or instances. We will address such cases -in the next sections. In the following example we define two new -value checkers on class nx::Slot. The first value checker is called -groupsize, the second one is called choice.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - | # -# Value checker named "groupsize" -# -::nx::Slot method type=groupsize {name value} { - if {$value < 1 || $value > 6} { - error "Value '$value' of parameter $name is not between 1 and 6" - } -} - -# -# Value checker named "choice" with extra argument -# -::nx::Slot method type=choice {name value arg} { - if {$value ni [split $arg |]} { - error "Value '$value' of parameter $name not in permissible values $arg" - } -} - -# -# Create an application class D -# using the new value checkers -# -nx::Class create D { - :public method foo {a:groupsize} { - # ... - } - :public method bar {a:choice,arg=red|yellow|green b:choice,arg=good|bad} { - # ... - } -} - -D create d1 - -# testing "groupsize" -d1 foo 2 -d1 foo 10 - -# testing "choice" -d1 bar green good -d1 bar pink bad |
In order to define a checker groupsize a method of the name -type=groupsize is defined. This method receives two arguments, -name and value. The first argument is the name of the parameter -(mostly used for the error message) and the second parameter is -provided value. The value checker simply tests whether the provided -value is between 1 and 3 and raises an exception if this is not the -case (invocation in line 36 in Listing 40).
The checker groupsize has the permissible values defined in its -method’s body. It is as well possible to define more generic checkers -that can be parameterized. For this parameterization, one can pass an -argument to the checker method (last argument). The checker choice -can be used for restricting the values to a set of predefined -constants. This set is defined in the parameter specification. The -parameter a of method bar in Listing 40 -is restricted to the values red, yellow or green, and the -parameter b is restricted to good or bad. Note that the syntax -of the permissible values is solely defined by the definition of the -value checker in lines 13 to 17. The invocation in line 39 will be ok, -the invocation in line 40 will raise an exception, since pink is not -allowed.
If the same checks are used in many places in the program, -defining names for the value checker will be the better choice since -it improves maintainability. For seldomly used kind of checks, the -parameterized value checkers might be more convenient.
3.7.5. Multiplicity
- -A multiplicity specification has a lower and an upper bound. A lower -bound of 0 means that the value might be empty. A lower bound of 1 -means that the parameter needs at least one value. The upper bound -might be 1 or n (or synonymously *). While the upper bound of -1 states that at most one value has to be passed, the upper bound of -n says that multiple values are permitted. Other kinds of -multiplicity are currently not allowed.
The multiplicity is written as parameter option in the parameter -specification in the form lower-bound..upper-bound. If no -multiplicity is defined the default multiplicity is 1..1, which -means: provide exactly one (atomic) value (this was the case in the -previous examples).
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - | nx::Object create o6 { - - # - # Positional parameter with an possibly empty - # single value - # - :public method foo {x:integer,0..1} { - puts "x=$x" - } - - # - # Positional parameter with an possibly empty - # list of values value - # - :public method bar {x:integer,0..n} { - puts "x=$x" - } - - # - # Positional parameter with a non-empty - # list of values - # - :public method baz {x:integer,1..n} { - puts "x=$x" - } -} |
Listing 41 contains three examples for -positional parameters with different multiplicities. Multiplicity is -often combined with value constraints. A parameter specification of -the form x:integer,0..n means that the parameter x receives a list -of integers, which might be empty. Note that the value constraints are -applied to every single element of the list.
The parameter specification x:integer,0..1 means that x might be -an integer or it might be empty. This is one style of specifying that -no explicit value is passed for a certain parameter. Another style is -to use required or optional parameters. NX does not enforce any -particular style for handling unspecified values.
All the examples in Listing 41 are for -single positional parameters. Certainly, multiplicity is fully -orthogonal with the other parameter features and can be used as well -for multiple parameters, non-positional parameter, default values, -etc.
4. Advanced Language Features
-…
4.1. Objects, Classes and Meta-Classes
-…
4.2. Resolution Order and Next-Path
-…
4.3. Details on Method and Object Parameters
-The parameter specifications are used in NX for the following -purposes. They are used for
-
-
-
-
-the specification of input arguments of methods and commands, for -
-
- -
-
-the specification of return values of methods and commands, and for -
-
- -
-
-the specification for the initialization of objects. -
-
-
We refer to the first two as method parameters and the last one as -object parameters. The examples in the previous sections all parameter -specification were specifications of method parameters.
The method parameter specify how methods are invoked, how the -actual arguments are passed to local variables of the invoked method -and what kind of checks should be performed on these.
Syntactically, object parameters and method parameters are the same, -although there are certain differences (e.g. some parameter options -are only applicable for objects parameters, the list of object -parameters is computed dynamically from the class structures, object -parameters are often used in combination with special setter methods, -etc.). Consider the following example, where we define the two -application classes Person and Student with a few properties.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - | # -# Define a class Person with properties "name" -# and "birthday" -# -nx::Class create Person { - :property name:required - :property birthday -} - -# -# Define a class Student as specialization of Person -# with and additional property -# -nx::Class create Student -superclass Person { - :property matnr:required - :property {oncampus:boolean true} -} - -# -# Create instances using object parameters -# for the initialization -# -Person create p1 -name Bob -Student create s1 -name Susan -matnr 4711 - -# Access property value via accessor method -puts "The name of s1 is [s1 name]" |
The class Person has two properties name and birthday, where the -property name is required, the property birthday is not. The -class Student is a subclass of Person with the additional required -property matnr and an optional property oncampus with the -default value true (see Listing 42). The class diagram below visualizes these -definitions.
In NX, these definitions imply that instances of the class of Person -have the properties name and birthday as non-positional object -parameters. Furthermore it implies that instances of Student will -have the object parameters of Person augmented with the object -parameters from Student (namely matnr and oncampus). Based on -these object parameters, we can create a Person named Bob and a -Student named Susan with the matriculation number 4711 (see line -23 and 24 in Listing 42). After the object s1 is created it has the -instance variables name, matnr and oncampus (the latter is -initialized with the default value).
4.3.1. Object Parameters for all NX Objects
-The object parameters are not limited to the application defined -properties, also NX provides some predefined definitions. Since -Person is a subclass of nx::Object also the object parameters of -nx::Object are inherited. In the introductory stack example, we used --mixin applied to an object to denote per-object mixins (see -Listing 8). Since mixin -is defined as a parameter on nx::Object it can be used as an object -parameter -mixin for all objects in NX. To put it in other words, -every object can be configured to have per-object mixins. If we would -remove this definition, this feature would be removed as well.
As shown in the introductory examples, every object can be configured -via a scripted initialization block (the optional scripted block -specified at object creation as last argument; see -Listing 5 or -Listing 12). The -scripted block and its meaning are as well defined by the means of -object parameters. However, this object parameter is positional (last -argument) and optional (it can be omitted). The following listing shows -(simplified) the object parameters of Person p1 and Student s1.
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - | Object parameter for Person p1: - -name:required -birthday ... - -mixin:mixinreg,alias,1..n -filter:filterreg,alias,1..n _:initcmd,optional - -Object parameter for Student s1: - -matnr:required {-oncampus:boolean true} - -name:required -birthday ... - -mixin:mixinreg,alias,1..n -filter:filterreg,alias,1..n _:initcmd,optional |
The actual values can be obtained via introspection via Person info -parameter definition. The object parameter types mixinreg and -filterreg are for converting definitions of filters and mixins. The -object parameter type initcmd says that the content of this variable -will be executed in the context of the object being created (before -the constructor init is called). More about the object parameter -types later.
4.3.2. Object Parameters for all NX Classes
-Since classes are certain kind of objects, classes are parameterized -in the same way as objects. A typical parameter for a class definition -is the relation of the class to its superclass.In our example, we have -specified, that Student has Person as superclass via the -non-positional object parameter -superclass. If no superclass is -specified for a class, the default superclass is -nx::Object. Therefore nx::Object is the default value for the -parameter superclass.
Another frequently used parameter for classes is -mixin to denote -per-class mixins (see e.g. the introductory Stack example in -Listing 10), which is defined in -the same way.
Since Student is an instance of the meta-class nx::Class it -inherits the object parameters from nx::Class (see class diagram -Figure 43). Therefore, one can -use e.g. -superclass in the definition of classes.
Since nx::Class is a subclass of nx::Object, the meta-class -nx::Class inherits the parameter definitions from the most general -class nx::Object. Therefore, every class might as well be configured -with a scripted initialization block the same way as objects can be -configured. We used actually this scripted initialization block in -most examples for defining the methods of the class. The following -listing shows (simplified) the parameters applicable for Class -Student.
1 - 2 - 3 - | Object parameter for Class Student: - -mixin:mixinreg,alias,1..n -filter:filterreg,alias,1..n ... - {-superclass:class,alias,1..n ::nx::Object} ... |
The actual values can be obtained via introspection via nx::Class info -parameter definition.
4.3.3. User defined Parameter Types
-More detailed definition of the object parameter types comes here.
4.3.4. Slot Classes and Slot Objects
-In one of the previous sections, we defined scripted (application -defined) checker methods on a class named nx::Slot. In general NX -offers the possibility to define value checkers not only for all -usages of parameters but as well differently for method parameters or -object parameters
4.3.5. Attribute Slots
-Still Missing
-
-
-
-
-return value checking -
-
- -
-
-switch -
-
- -
-
-initcmd … -
-
- -
-
-subst rules -
-
- -
-
-converter -
-
- -
-
-incremental slots -
-
-
5. Miscellaneous
-5.1. Profiling
-5.2. Unknown Handlers
-NX provides two kinds of unknown handlers:
-
-
-
-
-Unknown handlers for methods -
-
- -
-
-Unknown handlers for objects and classes -
-
-
5.2.1. Unknown Handlers for Methods
-Object and classes might be equipped -with a method unknown which is called in cases, where an unknown -method is called. The method unknown receives as first argument the -called method followed by the provided arguments
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - | ::nx::Object create o { - :method unknown {called_method args} { - puts "Unknown method '$called_method' called" - } -} - -# Invoke an unknown method for object o: -o foo 1 2 3 - -# Output will be: "Unknown method 'foo' called" |
Without any provision of an unknown method handler, an error will be -raised, when an unknown method is called.
5.2.2. Unknown Handlers for Objects and Classes
-The next scripting framework provides in addition to unknown method -handlers also a means to dynamically create objects and classes, when -these are referenced. This happens e.g. when superclasses, mixins, or -parent objects are referenced. This mechanism can be used to implement -e.g. lazy loading of these classes. Nsf allows to register multiple -unknown handlers, each identified by a key (a unique name, different -from the keys of other unknown handlers).
1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - | ::nx::Class public class method __unknown {name} { - # A very simple unknown handler, showing just how - # the mechanism works. - puts "***** __unknown called with <$name>" - ::nx::Class create $name -} - -# Register an unknown handler as a method of ::nx::Class -::nsf::object::unknown::add nx {::nx::Class __unknown} - -::nx::Object create o { - # The class M is unknown at this point - - :mixin add M - # The line above has triggered the unknown class handler, - # class M is now defined - - puts [:info mixin classes] - # The output will be: - # ***** __unknown called with <::M> - # ::M -} |
The Next Scripting Framework allows to add, query, delete and list unknown handlers.
1 - 2 - 3 - 4 - 5 - | # Interface for unknown handlers: -# nsf::object::unknown::add /key/ /handler/ -# nsf::object::unknown::get /key/ -# nsf::object::unknown::delete /key/ -# nsf::object::unknown::keys |
-
-
-
-
- U. Zdun, M. Strembeck, G. Neumann: - Object-Based and Class-Based Composition of Transitive Mixins, - Information and Software Technology, 49(8) 2007 . -
-
- -
-
- G. Neumann and U. Zdun: Filters as a - language support for design patterns in object-oriented scripting - languages. In Proceedings of COOTS’99, 5th Conference on - Object-Oriented Technologies and Systems, San Diego, May 1999. -
-
- -
-
- G. Neumann and U. Zdun: Implementing - object-specific design patterns using per-object mixins. In Proc. of - NOSA`99, Second Nordic Workshop on Software Architecture, Ronneby, - Sweden, August 1999. -
-
- -
-
- G. Neumann and U. Zdun: Enhancing - object-based system composition through per-object mixins. In - Proceedings of Asia-Pacific Software Engineering Conference (APSEC), - Takamatsu, Japan, December 1999. -
-
- -
-
- G. Neumann and U. Zdun: XOTCL, an - object-oriented scripting language. In Proceedings of Tcl2k: The - 7th USENIX Tcl/Tk Conference, Austin, Texas, February 2000. -
-
- -
-
- G. Neumann and U. Zdun: Towards the Usage - of Dynamic Object Aggregations as a Form of Composition In: - Proceedings of Symposium of Applied Computing (SAC’00), Como, - Italy, Mar 19-21, 2000. -
-
- -
-
- G. Neumann, S. Sobernig: XOTcl 2.0 - A - Ten-Year Retrospective and Outlook, in: Proceedings of the Sixteenth - Annual Tcl/Tk Conference, Portland, Oregon, October, 2009. -
-
- -
-
- J. K. Ousterhout: Tcl: An embeddable command - language. In Proc. of the 1990 Winter USENIX Conference, January 1990. -
-
- -
-
- J. K. Ousterhout: Scripting: Higher Level - Programming for the 21st Century, IEEE Computer 31(3), March 1998. -
-
- -
-
- D. Wetherall and C. J. Lindblad: Extending Tcl for - Dynamic Object-Oriented Programming. Proc. of the Tcl/Tk Workshop '95, - July 1995. -
-
-