Objects in Tcl

Abstract:

For a long time, Tcl did not have a native object model. But the flexibility of the language allows you to easily implement your own. We will also look briefly at some of the existing object-oriented extensions for Tcl.

Introduction

C++, C#, Python, Java and many other programming languages offer support for object oriented programming. Even without explicit language support, objects can easily be simulated. Object orientation is really just a way of thinking; it has more to do with design than with implementation. Look at the source code of the Tcl interpreter itself for a great example of object oriented programming in C (not C++!). If you are disciplined enough to always pass a pointer to a struct as the first parameter to a function, you can see such a function as a 'method' of the struct. You do not need actual language support to create object-oriented code. Even inheritance and polymorphism can be mimicked by clever use of function pointers.

Before version 8.6, Tcl did not offer object oriented primitives. This article presents a number of simple techniques to add objects to Tcl. Once you understand these techniques, you will be able to figure out most of the existing object packages and extensions.

Existing extensions

Before version 8.6, Tcl did not have a built-in object system. Over the years, people have filled this gap with a large number of extensions. Some of these extensions are written in C, and must be compiled to make them available in the Tcl language. A good example is [Incr Tcl], an extension that introduces primitives such as class, method and constructor.

Other extensions, such as Jean-Luc Fontaine's Stooop, are written in Tcl itself. They don't require any compilation. You may wonder how it is possible to extend Tcl with new primitives written in Tcl itself. This article answers that question by zooming in on the techniques of object commands and class commands. We will not discuss extensions that require compilation.

We should also distinguish between static and dynamic classes. With static classes, the members of a class cannot be changed at runtime. You can introduce new classes into a running system, but once a class is created, you cannot add new methods or data members to it. Similarly, you can create new instances of a class, but you cannot (easily) change the class of an existing instance, or give it instance-specific methods or member variables.

[Incr Tcl] is an example of a Tcl extension with static classes. You cannot add methods or variables to an existing class or object. You can, however, change the implementation of any method of a class (just like rewriting the body of an existing procedure in Tcl). And of course, you can inherit from an existing class and add new methods and variables in the derived class.

But since Tcl is a dynamic language, in which you can introduce new procedures and new variables at run-time, it seems more appropriate to also allow the creation of new methods and member variables at run-time. That requires a dynamic class mechanism such as offered by OTcl.

Both static and dynamic object-oriented extensions of Tcl can make use of the techniques described in this article.

A simple example

We will slowly introduce the necessary techniques, starting from an extremely simple example.

Suppose we want to write a script that stores information about fruit. We want to manipulate objects such as apples and lemons. We intend to give each object a unique identifier or number to distinguish it from other objects. We also want to store some properties for each object, such as their color.

As a first approach, we could store the object colors in a Tcl array, indexed by the object name. For example:

 1 set a_color(a1) green
 2 set a_color(a2) yellow
 3 set a_color(a3) red

We now have three objects a1, a2, a3, each with their own color. Even an extremely simple approach like this one is already useful in many cases where you need to map object attributes to their values.

We can make this more attractive by hiding the array, using two access procedures:

example_01/apples.tcl
 1 proc get_color {obj_name} {
 2    global a_color
 3    if { [info exists a_color($obj_name)] } {
 4       return $a_color($obj_name)
 5    } else {
 6       puts "Warning: $obj_name has no color!"
 7       return "transparent"   ; # return a default color
 8    }
 9 }
10 
11 proc set_color {obj_name color} {
12    global a_color
13    set a_color($obj_name) $color
14 }

We now access the colors of objects as follows:

example_01/apples.tcl
 1 set_color a1 green
 2 puts "a1 has color [get_color a1]"

The next step is to introduce some syntactic sugar: just a small improvement that makes the syntax look better, but does not really change anything fundamentally. We create the following procedure:

example_02/apples.tcl
 1 proc a1 {command args} {
 2    if { $command == "get_color" } {
 3       return [get_color a1]
 4    } elseif { $command == "set_color" } {
 5       set_color a1 [lindex $args 0]
 6    } else {
 7       puts "Error: Unknown command $command"
 8    }
 9 }

Using this procedure, we can now access the color of the 'a1' object as follows:

example_02/apples.tcl
 1 a1 set_color yellow
 2 puts "a1 has color [a1 get_color]"

With respect to the earlier example, we basically swapped the positions of the object name with the name of the get_color or set_color procedure. Not very useful in itself, but it makes the syntax look more like an object invocation. It looks as if we invoke the "method" set_color on the "object" a1.

Procedure a1 is called an object command or instance command. Its name is the name of an object. Its first argument is the name of a method that you want to invoke on the object. The object's data is stored in a global array, in this case a_color, but that is hidden from the programmer by the object command.

We can now create as many objects as we want: just write a procedure like a1 for each object, each with a different name. Sounds like a lot of work? It is. We will soon see how you can automate this. Writing a separate procedure for every object is not only tiresome; it also imposes heavy resource burdens on the application, because procedures take up space in the Tcl interpreter.

The first improvement is that we can write a single dispatcher procedure like this one:

example_03/apples.tcl
 1 proc dispatch {obj_name command args} {
 2    if { $command == "get_color" } {
 3       return [get_color $obj_name]
 4    } elseif { $command == "set_color" } {
 5       set_color $obj_name [lindex $args 0]
 6    } else {
 7       puts "Error: Unknown command $command"
 8    }
 9 }

The object commands can now be written with only a single line of code:

example_03/apples.tcl
 1 proc a1 {command args} {
 2    return [eval dispatch a1 $command $args]
 3 }

Creating a procedure of this form for each object consumes less memory, simply because the procedure is shorter. But it is still quite cumbersome to write a procedure every time you want to instantiate an object. To simplify this task, we write yet another procedure, one that creates object commands! It looks like this:

example_04/apples.tcl
 1 proc apple {args} {
 2    foreach name $args {
 3       proc $name {command args} \
 4          "return \[eval dispatch $name \$command \$args\]"
 5    }
 6 }

We call this procedure the class command, because it is like a class type that you can instantiate. Instantiating and manipulating objects is now as simple as this:

example_04/apples.tcl
 1 apple a1 a2 a3
 2 a1 set_color green
 3 a2 set_color yellow
 4 a3 set_color red
 5 puts "a1 has color [a1 get_color]"
 6 puts "a2 has color [a2 get_color]"
 7 puts "a3 has color [a3 get_color]"

The class command creates objects of class 'apple'. Each apple has its own color, which can be accessed through the methods get_color and set_color of the class.

There are still some pieces missing in the puzzle. First of all, we now have a way of creating new objects, but we cannot delete objects yet. This leads to memory leaks, so we need to provide a procedure for deleting apples:

example_05/apples.tcl
 1 proc delete_apple {args} {
 2    global a_color
 3    foreach name $args {
 4       unset a_color($name)   ; # Deletes the object's data
 5       rename $name {}   ; # Deletes the object command
 6    }
 7 }

We can also set up the array a_color in such a way that $a_color(obj) is always initialized with a default value for every object. We do this in the class command, by setting the default color to green:

example_05/apples.tcl
 1 proc apple {args} {
 2    global a_color
 3    foreach name $args {
 4       proc $name {command args} \
 5          "return \[eval dispatch $name \$command \$args\]"
 6       set a_color($name) green
 7    }
 8 }

This makes the class command act like a constructor that sets up the default values for object attributes. In this case we picked green as the default color for apples. We now use the complete set of procedures like this:

example_05/apples.tcl
 1 apple a1 a2 a3
 2 a2 set_color yellow
 3 a3 set_color red
 4 puts "a1 has color [a1 get_color]"   ; # Uses default color green
 5 puts "a2 has color [a2 get_color]"
 6 puts "a3 has color [a3 get_color]"
 7 delete_apple a1 a2 a3   ; # Delete the objects.

Summary so far

To summarize, we have followed these steps:

The resulting object system is still a bit too simple, but all the basic techniques are there. You now know enough to start using object commands and class commands in Tcl. The rest of this article refines the techniques, offers a few more tips and tricks, and gives (pointers to) real-life examples where object commands are used.


More attributes

So far, apples only have a color. We will now give our apple class a few more attributes: a size and a price (both are integers for simplicity). These are again stored in global arrays, for example a_size and a_price. Both are indexed by the name of the object, just as for the a_color array we've been using so far. And again we can write get/set procedures to access these new attributes. The code is very similar to that for the color attribute, so I will not show it here.

An interesting (and frankly, much better) alternative is to use an array for every object, rather than an array for every attribute. Tcl allows us to create a procedure and an array variable with the same name, so we can call our object command 'a1' and use an array 'a1' to store the attributes of that object. The code of all our procedures now changes slightly, because we have to index the object's array with the attribute name, rather than the attribute's array with the object name:

example_06/apples.tcl
 1 # We now use the object name as an array, not the attribute name.
 2 # This allows us to easily store and retrieve multiple attributes per object.
 3 # To delete an object, just delete its array and its object command.
 4 
 5 proc get_color {obj_name} {
 6    upvar #0 $obj_name arr
 7    return $arr(color)
 8 }
 9 
10 proc set_color {obj_name color} {
11    upvar #0 $obj_name arr
12    set arr(color) $color
13 }
14 
15 proc dispatch {obj_name command args} {
16    if { $command == "get_color" } {
17       return [get_color $obj_name]
18    } elseif { $command == "set_color" } {
19       set_color $obj_name [lindex $args 0]
20    } else {
21       puts "Error: Unknown command $command"
22    }
23 }
24 
25 proc apple {args} {
26    foreach name $args {
27       proc $name {command args} \
28          "return \[eval dispatch $name \$command \$args\]"
29       upvar #0 $name arr
30       set arr(color) green
31    }
32 }
33 
34 proc delete_apple {args} {
35    foreach name $args {
36       upvar #0 $name arr
37       unset arr        ; # Deletes the object's data
38       rename $name {}  ; # Deletes the object command
39    }
40 }
41 
42 # Note the advantage of using an array per object:
43 # 'delete_apple' can just 'unset arr' instead of having to
44 # remove one entry in three different arrays.

A third alternative is to use only a single, global array, indexed by the object name and the attribute name. To find the color of object a1, you would have to access $all_attributes(a1,color). The advantage of having only a single array to maintain, has to be weighed off against the disadvantage of having to delete several array entries when deleting an object.

Configuring the attributes

Another improvement that we can make, is to get rid of all those annoying get/set methods. We do this by introducing two new methods for each class, called configure and cget. The first gives new values to some attributes, the second reads the value of an attribute. Their names are chosen to reflect similar commands in Tk. We can implement these procedures for the apple class as follows:

example_07/apples.tcl
 1 # Handling multiple attributes with 'configure' and 'cget'.
 2 
 3 proc dispatch {obj_name command args} {
 4    upvar #0 $obj_name arr
 5    if { $command == "configure" || $command == "config" } {
 6       foreach {opt val} $args {
 7          if { ![regexp {^-(.+)} $opt dummy small_opt] } {
 8             puts "Wrong option name $opt (ignored)"
 9          } else {
10             set arr($small_opt) $val
11          }
12       }
13 
14    } elseif { $command == "cget" } {
15       set opt [lindex $args 0]
16       if { ![regexp {^-(.+)} $opt dummy small_opt] } {
17          puts "Wrong or missing option name $opt"
18          return ""
19       }
20       return $arr($small_opt)
21 
22    } elseif { $command == "byte" } {
23       puts "Taking a byte from apple $obj_name ($arr(size))"
24       incr arr(size) -1
25       if { $arr(size) <= 0 } {
26          puts "Apple $obj_name now completely eaten!  Deleting it..."
27          delete_apple $obj_name
28       }
29 
30    } else {
31       puts "Error: Unknown command $command"
32    }
33 }
34 
35 # We also change the implementation of the "constructor",
36 # so that it accepts initializing values for the attributes.
37 proc apple {name args} {
38    proc $name {command args} \
39       "return \[eval dispatch $name \$command \$args\]"
40 
41    # First set some defaults
42    upvar #0 $name arr
43    set arr(color) green
44    set arr(size) 5
45    set arr(price) 10
46 
47    # Then possibly override those defaults with user-supplied values
48    if { [llength $args] > 0 } {
49       eval $name configure $args
50    }
51 }

The constructor now creates only a single object, but allows you to specify its attribute values right there in the constructor call. Attribute access now looks exactly as it does for Tk widgets. Compare these two fragments of code:

 1 # Tk button object.
 2 button .b -text "Hello" -command "puts world"
 3 .b configure -command "exit"
 4 set textvar [.b cget -text]
 5 
 6 # Our own apple object.
 7 apple a -color red -size 5
 8 a configure -size 6
 9 set clr [a cget -color]

Some widget libraries that are written in pure Tcl, use object commands and configure/cget methods to make the widget syntax the same as in Tk. Obviously, this technique also works for other kinds of objects.

Object persistence

We will now cover a more exotic topic: object persistence. This means that you can save an object on disk, and recover it later, in the same or in another application. The recovered object has exactly the same attribute values as the one you saved.

In languages such as C++, object persistence is quite a challenge (especially if you want to save an object on one platform, and recover it on another platform with different endianness or with a different compiler). But the flexibility of Tcl, and the fact that everything is a string, makes object persistence a piece of cake! We will save our objects in a text file, then treat that file as an Active File to read the objects back. Read more about the Active File pattern in my article on Tcl file formats.

We only need a single Tcl procedure (!) to give objects of all classes the ability to make themselves persistent:

example_08/apples.tcl
 1 proc write_objects {classname args} {
 2    foreach name $args {
 3       upvar #0 $name arr
 4       puts "$classname $name \\"
 5       foreach attr [array names arr] {
 6          puts "   -$attr $arr($attr) \\"
 7       }
 8       puts ""
 9    }
10 }

The idea is to invoke this procedure as follows:

example_08/write_apples.tcl
 1 write_objects apple a1 a2 a3

The implementation above shows that the procedure makes the objects a1, a2, and a3 persistent, by simply outputting a call to the constructor with the object name and all its attributes. The resulting output is stored in a data file and looks like this:

example_08/datafile.dat
 1 apple a1 \
 2    -price 10 \
 3    -size 5 \
 4    -color green \
 5 
 6 apple a2 \
 7    -price 10 \
 8    -size 3 \
 9    -color yellow \
10 
11 apple a3 \
12    -price 12 \
13    -size 5 \
14    -color red \
15 

It is now extremely easy to read these persistent objects back from disk: just source the file! Tcl's source command executes all constructor calls in the file, creating instances with exactly the same attributes as the ones we saved earlier. Object persistence in Tcl is indeed a piece of cake.

Note that we only need a single procedure write_objects to persist objects of any class. The procedure simply outputs the class name before every object name.

Also note that we would have to add quotes around the attribute values to support values containing spaces.

Adding new classes

So far, we have worked with only a single class apple. If we want to add a new class to our example, we need to write a new class command and a new dispatcher procedure.

Suppose we also want to have objects of class fridge (in which we will want to store apples of course). We need to duplicate the effort we did on the apple class:

example_10/classes.tcl
 1 # Dispatch procedure for class 'fridge'.
 2 proc dispatch_fridge {obj_name command args} {
 3    upvar #0 $obj_name arr
 4    if { $command == "configure" || $command == "config" } {
 5       array set arr $args
 6 
 7    } elseif { $command == "cget" } {
 8       return $arr([lindex $args 0])
 9 
10    } elseif { $command == "open" } {
11       if { $arr(-state) == "open" } {
12          puts "Fridge $obj_name already open."
13       } else {
14          set arr(-state) "open"
15          puts "Opening fridge $obj_name..."
16       }
17 
18    } elseif { $command == "close" } {
19       if { $arr(-state) == "closed" } {
20          puts "Fridge $obj_name already closed."
21       } else {
22          set arr(-state) "closed"
23          puts "Closing fridge $obj_name..."
24       }
25 
26    } else {
27       puts "Error: Unknown command $command"
28    }
29 }
30 
31 # Class procedure for class 'fridge'.
32 proc fridge {name args} {
33    proc $name {command args} \
34       "return \[eval dispatch_fridge $name \$command \$args\]"
35 
36    # First set some defaults
37    upvar #0 $name arr
38    array set arr {-state closed -label A}
39 
40    # Then possibly override those defaults with user-supplied values
41    if { [llength $args] > 0 } {
42       eval $name configure $args
43    }
44 }

These two procedures are almost exactly the same as for apples, and we have to repeat the work for every class we add to our script. This laborious task can be partly automated by a procedure called class which accepts the name of a new class, a list of its member variables, and a list of its method names. It then automatically sets up the necessary procedures such as the class command and the dispatcher procedure. The only thing we still need to implement by hand, are the methods of the class. The whole thing could be set up as follows:

example_11/classes.tcl
 1 proc class {classname vars methods} {
 2 
 3    # Create the class command, which will allow new instances to be created.
 4    proc $classname {obj_name args} "
 5       # The class command in turn creates an object command.  Careful
 6       # with those escape characters!
 7       proc \$obj_name {command args} \
 8          \"return \\\[eval dispatch_$classname \$obj_name \\\$command \\\$args\\\]\"
 9 
10       # Set variable defaults
11       upvar #0 \$obj_name arr
12       array set arr {$vars}
13 
14       # Then possibly override those defaults with user-supplied values
15       if { \[llength \$args\] > 0 } {
16          eval \$obj_name configure \$args
17       }
18    "
19 
20    # Create the dispatcher, which dispatches to one of the class methods
21    proc dispatch_$classname {obj_name command args} "
22       upvar #0 \$obj_name arr
23       if { \$command == \"configure\" || \$command == \"config\" } {
24          array set arr \$args
25 
26       } elseif { \$command == \"cget\" } {
27          return \$arr(\[lindex \$args 0\])
28 
29       } else {
30          if { \[lsearch {$methods} \$command\] != -1 } {
31             uplevel 1 ${classname}_\${command} \$obj_name \$args
32          } else {
33             puts \"Error: Unknown command \$command\"
34          }
35       }
36    "
37 }

The class procedure basically just creates two new commands for us (a class command and a dispatcher). These are the 2 commands that we had to create by hand earlier.

The code looks pretty messy, because it contains two levels of indirection: a proc that creates a proc that creates yet another proc. This involves a bit of backslash-escape magic, which can be confusing. Richard Suchenwirth has a very nice solution to make this kind of code more readable: he creates a template with names containing a special character such as '@'. Then he replaces those names by the actual class and instance names, using regsub. See his page on gadgets for an example. Using this technique, our implementation becomes a lot easier to read:

example_12/classes.tcl
 1 proc class {classname vars methods} {
 2 
 3    # Create the class command, which will allow new instances to be created.
 4    set template {
 5       proc @classname@ {obj_name args} {
 6          # The class command in turn creates an object command.
 7          # Fewer escape characters thanks to the '@' sign.
 8          proc $obj_name {command args} \
 9             "return \[eval dispatch_@classname@ $obj_name \$command \$args\]"
10 
11          # Set variable defaults
12          upvar #0 $obj_name arr
13          array set arr {@vars@}
14 
15          # Then possibly override those defaults with user-supplied values
16          if { [llength $args] > 0 } {
17             eval $obj_name configure $args
18          }
19       }
20    }
21 
22    regsub -all @classname@ $template $classname template
23    regsub -all @vars@ $template $vars template
24 
25    eval $template
26 
27    # Create the dispatcher, which dispatches to one of the class methods
28    set template {
29       proc dispatch_@classname@ {obj_name command args} {
30          upvar #0 $obj_name arr
31          if { $command == "configure" || $command == "config" } {
32             array set arr $args
33 
34          } elseif { $command == "cget" } {
35             return $arr([lindex $args 0])
36 
37          } else {
38             if { [lsearch {@methods@} $command] != -1 } {
39                uplevel 1 @classname@_${command} $obj_name $args
40             } else {
41                puts "Error: Unknown command $command"
42             }
43          }
44       }
45    }
46 
47    regsub -all @classname@ $template $classname template
48    regsub -all @methods@ $template $methods template
49 
50    eval $template
51 }

You see that this simplifies the code. We use the '@' sign because it is not frequently used in normal Tcl code. We postpone the evaluation of $classname and other variables until we are out of the inner procedure body, so that the number of escape characters is reduced to almost zero.

With or without this "template" technique, we can now create our original classes apple and fridge in a much more compact way:

example_12/classes.tcl
 1 # Create a class with 3 attributes and a 'byte' method.
 2 class apple {-color green -size 5 -price 10} {byte}
 3 proc apple_byte {self} {
 4    upvar #0 $self arr
 5    puts "Taking a byte from apple $self"
 6    incr arr(-size) -1
 7    if { $arr(-size) <= 0 } {
 8       puts "Apple $self now completely eaten!  Deleting it..."
 9       delete $self
10    }
11 }
12 
13 # Create a class with 2 attributes and 2 methods.
14 class fridge {-state closed -label A} {open close}
15 proc fridge_open {self} {
16    upvar #0 $self arr
17    if { $arr(-state) == "open" } {
18       puts "Fridge $self already open."
19    } else {
20       set arr(-state) "open"
21       puts "Opening fridge $self..."
22    }
23 }
24 
25 proc fridge_close {self} {
26    upvar #0 $self arr
27    if { $arr(-state) == "closed" } {
28       puts "Fridge $self already closed."
29    } else {
30       set arr(-state) "closed"
31       puts "Closing fridge $self..."
32    }
33 }

Creating new classes is indeed a lot simpler than before. We only need one line with the class "declaration" containing its attribute names, plus one proc for each of the class methods. Each method is implemented as a global proc which has the instance name as its first parameter. The parameter is called "self", which is only a convention. Any other arguments are optional.

In the implementation of each method, we access the object's array explicitly. We could make the methods less dependent on the actual implementation of the object by using configure and cget instead, for example

example_13/classes.tcl
 1 proc fridge_close {self} {
 2    if { [$self cget -state] == "closed" } {
 3       puts "Fridge $self already closed."
 4    } else {
 5       $self configure -state "closed"
 6       puts "Closing fridge $self..."
 7    }
 8 }

This depends less on our array implementation, and is perhaps slightly more readable. It is less efficient though, because the configure and cget implementations add an extra level of procedure calls with a couple of ifs. You should probably decide for yourself which of the two ways you are going to use, depending on the importance of efficiency in your application.

Note that we can implement the class procedure in a slightly different way, without actually knowing in advance the list of all the variables and methods of the class. The new implementation could look like this:

example_14/classes.tcl
 1 # No more 'methods' argument here; 'vars' is optional.
 2 proc class {classname {vars ""}} {
 3 
 4    # Create the class command, which will allow new instances to be created.
 5    set template {
 6       proc @classname@ {obj_name args} {
 7          # The class command in turn creates an object command.
 8          # Fewer escape characters thanks to the '@' sign.
 9          proc $obj_name {command args} \
10             "return \[eval dispatch_@classname@ $obj_name \$command \$args\]"
11 
12          # Set variable defaults, if any
13          upvar #0 $obj_name arr
14          @set_vars@
15 
16          # Then possibly override those defaults with user-supplied values
17          if { [llength $args] > 0 } {
18             eval $obj_name configure $args
19          }
20       }
21    }
22 
23    set set_vars "array set arr {$vars}"
24    regsub -all @classname@ $template $classname template
25    if { $vars != "" } {
26       regsub -all @set_vars@  $template $set_vars template
27    } else {
28       regsub -all @set_vars@  $template "" template
29    }
30 
31    eval $template
32 
33    # Create the dispatcher, which does not check what it
34    # dispatches to.  It just follows the naming convention.
35    set template {
36       proc dispatch_@classname@ {obj_name command args} {
37          upvar #0 $obj_name arr
38          if { $command == "configure" || $command == "config" } {
39             array set arr $args
40 
41          } elseif { $command == "cget" } {
42             return $arr([lindex $args 0])
43 
44          } else {
45             # Here you see the naming convention explicitly: classname_command.
46             uplevel 1 @classname@_${command} $obj_name $args
47          }
48       }
49    }
50 
51    regsub -all @classname@ $template $classname template
52 
53    eval $template
54 }
55 
56 fridge f1 -state open
57 f1 close
58 
59 # Even after 'f1' is created, we can add a new method to the 'fridge'
60 # class.  'f1' automatically gets the new method.
61 proc fridge_paint {self color} {
62    puts "Painting fridge $self $color ..."
63 }
64 
65 f1 paint green

This implementation shows that you can add new methods to an existing class, simply by implementing a new global procedure following a classname_methodname naming convention, with self as its first argument. The dispatcher procedure will find this new method even though it did not yet exist at the time the class was created. The same is true for member variables (this has silently been the case in all previous examples): just call configure with a new variable name, and it will end up in the object's array. Only variables specified in the class procedure get a default value, though; Other variables do not exist before they are first set by configure!


Advanced techniques

This is not the full story on Tcl objects. There are many more techniques that you can experiment with. There are also many existing object systems for Tcl that you can study, and they use many variations on the techniques we looked at above. I won't go into detail here; I just provide an overview of some of the possibilities.

Memory leaks in Tcl

Instead of storing object attributes in global arrays, you could create local arrays and pass them around with upvar. That way, these arrays disappear when they go out of scope. This stops a lot of memory leaks. You can set a trace on the array so that when it goes out of scope, you also delete the object command. Thanks to Richard Suchenwirth for this tip.

Introspection

In the spirit of Tcl, classes and instances should offer introspection. For example, you should be able to:

Persistence revisited

From object-based to object-oriented

Destructors, virtual functions, inheritance, delegates, properties, operator overloading, ... There are many other object-oriented features that we have not yet discussed in this article. The Tcl'ers Wiki has many cool articles that cover such techniques. Here are only a few:

Namespaces

To avoid name collisions, classes and instances should be defined in namespaces. Will Duquette's article covers this in detail.


Reference links

Many object-oriented extensions for Tcl have been implemented, and since version 8.6, Tcl even has a built-in object system. Find out more about all these object systems at the following pages:

Many thanks to everybody who read the first drafts of this article and helped me correct some mistakes and make many improvements. Special thanks to Richard Suchenwirth, Jean-Luc Fontaine, and Bob Techentin for their technical insights.