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. +
+
+