OO libraries

An object orientation in the spirit of TCL

There is lot of discussion on whether to add OO in TCL base or not.

The best way to resolve this is to start developing libraries like "collector classes," e.g., a generic foreach command for lists, arrays, etc. and let people start using them. More the libraries and more people start using them, then things will find its place naturally.

The OO should not be inconsistent with TCL semantics. The first word should be a "command". Most OO implementations make the first word "the object".

It should have TCL spirit. The base should have minimal stuff. Impatient people can skip the philosophy section to go to the concrete stuff.

All it requires is to have a procedure with name ">". The definition of "proc >" is given below. This procedure is some 4 lines. It is more of a glueing standard, the class/type implementation can be in any language, any manner.

OO philosophy


Why object orientation? Thoughts on OO, Natural language, human thinking. To put it simply, TCL is a simple language. For this language, simplicity should not increase application complexity. The more one interacts with TCL, the more "verbs" (commands) one needs to know. This causes cognitive load.

In an analogy with natural language, more new products come in, but the number of "verbs" does not explode in the language. When you say "open the door", "open the letter", "open the computer", "open" is the verb. A verb represents an action. More precisely, this action can be thought of as a sequence of actions. More precisely, if sequence of actions are mapped to verbs, then we will have "open_door", "open_letter", "open_computer" verbs. This is because the sequence of actions are different for "open_door", "open_letter". But we don't use it that way. Therefore the "open" verb is a kind of generic "verb" that maps to a specific verb "open_door" or "open_letter" based on the the type of object. This mapping happens implicitly in our mind. Natural language evolution was based on the cognitive limits of human mind.

The same way, TCL has commands. They are more like "verbs" but "specific verbs". They map to a unique sequence of actions defined in proc. We need TCL to support "generic commands" something like "generic verbs", we use commonly in natural language. This should be "TCL way of object orientation", I feel.


Example usage

Here is an example. Presently TCL supports data encapsulation this way. Say I have an implementation of counter, I use it this way

 counter_create a 
 counter_add a 1 # a will have 1
 counter_add a 3 # a will have 4
 counter_sub a 1 # a will have 3

 # For lists
 set a [ list {} ]
 lappend a ram # a will be { ram }
 lappend a somu # a will be { ram somu }
 lvarpop a 0 # a will be { somu }

Now there are different commands for different type of data/object. In the new model, here is how the interaction will be. All "generic commands" will be prefixed by ">". Note: ">" is not a prompt.

 counter create  a 
 > add a 1 # a will have 1
 > add a 3 # a will have 4
 > sub a 1 # a will have 3

 # For lists
 mylist create a 
 > add a ram # a will be { ram }
 > add a somu # a will be { ram somu }
 > sub a 0 # a will be { somu }

Now see the difference. If you are teaching TCL to a school kid, they will surely see the difference in cognitive comfort in two modes. More than cognitive comfort, there are other productivity benefits too. Say I can create another generic command "sum" that can receive a list of objects which can act on "add" command. Rest is OO benefits .. I need not dwell on that.

Implementation


To implement generic commands that can map to specific commands based on object type or data type. We need to have types and the object should carry directly/indirectly the type information. C implements user-defined types by struct. The same way we can define it with arrays. What is more important is to get the type information from the array. All objects are of type array with a minimum of one element having the key "handler". Rest of the array elements are instance variables.

In the above "a" object is an an array. It has a key "handler". In case of counter object, the handler value is "counter". In case of mylist object, the handler value is "mylist". Here typehandler, indirectly denotes the type. (Analogous to #! giving the handler for files in Unix )

Here is the implementation of "proc >"

 proc > { args } {
  set obj [lindex $args 1 ] # get the object reference
  upvar $obj myobj
  set handler $myobj(handler) # get the handler for the object
  eval "$handler [join $args]" # invoke the handler and pass the request
 }

The typehandler, type, class all refer to the same thing. It is a proc. It sees the command and dispatches to the relevent code-block implemented as switch. This implementation can be done any way desired. An example implementation of counter type.

Here is the implementation of type counter:

  proc counter { args } {
    set ref [lindex $args 1 ] 
    set action [lindex $args 0] 
    if [ string equal $action create ] { 
     upvar 1 $ref myref
     } else  { 
      upvar 2 $ref myref
     }
      # method dispatching is done by switch.

      switch $action \
           create { 
            array set myref [list handler counter]
            array set myref [list count 0 ]
           }\
           add  { 
                  # instance variables are nothing but array elements, 
                  # here array key count holds an instance data.

            set incr [lindex $args 2 ]
            set pres_val $myref(count)
            set new_val [ expr $pres_val + $incr ]
            array set myref [list count $new_val ]
               }\
           sub {
             set decr [lindex $args 2 ]
             set pres_val $myref(count)
             set new_val [ expr $pres_val - $decr ]
             array set myref [list count $new_val ]
           }\
           get {

                   return $myref(count)
           } \
           reset {
              array set myref [ list count 0 ]
           }
 }

Implementing inheritance.

By adding default clause to the switch and invoking a parent handler in the default clause, inheritance can be implemented.

Implementing type handler in C

It is easy to implement the handler in "C" also. The "C" program needs to register only one procedure, i.e., the handler procedure. The object should be an array with key "handler", having the value as the name of the procedure. The method dispatching should be done by the "C" handler procedure.

An example implementation of mylist

 package require Tclx
 proc mylist { args } {
    set ref [lindex $args 1 ]
    set action [lindex $args 0]
    if [ string equal $action create ] { 
     upvar 1 $ref myref 
     } else  { 
      upvar 2 $ref myref
     }       

      switch $action \
           create {  # instance variable is kept in the key "list"
            array set myref [list handler mylist]
            array set myref [list list {}] 
           }\
           add  {
            set elem [lindex $args 2 ]
            lappend myref(list) $elem
            # array set myref [list list $new_list ]
               }\
           sub {
             set index [lindex $args 2 ]
                    set index [lindex $args 2 ]
             lvarpop myref(list) $index
           #  array set myref [list list $new_list ]
           }\
           get {

                   return $myref(list)
           } \
           reset {
              array set myref [ list list {} ]
           }

 }

The standard

This is more of a glueing standard and leaves room for different implementations. Since TCL is a glueing language. Implementation can be done in "C" or whatever. Here is a more precise definition of the standard.

There are "generic commands" besides the normal commands of TCL. The generic commands are invoked with the following syntax. Note all generic commands invocation begin with ">"

 > %command_name% %object_name% %other_args% ..

The object is an array with minimum one key called "handler". Instance variables can be implemented as other array keys. The handler gives the type-handler to handle the command.

Handlers can be implemented in many ways. It is typically a proc. As time goes, I will add more type implementations. I prefer to use the term "type" instead of "class".

Some later thoughts ...It seems syntax need not be standardised but what must be standardised is Object Representation.See discussion below.

Some Type Implementations

vkvalli


Discussions

RS So, to summarize, you propose:

  • one proc per class as constructor and method dispatcher
  • one array per object, which identifies which class it belongs to
  • a ">" command for all OO actions

For the latter, you can easily have the usual $object method arg.. style as well, with

 interp alias {} $name {} > $name

But it takes one extra array lookup to determine the class handler.


vkvalli After some thought, this what I concluded.

  • object-method invocation - format need not be standardised, that again can be changed to suit people's taste, whether ">" string or some other string. But I feel it is better to have "verb-object" style. For a more elaborate discussion on this, see Thoughts on OO, Natural language, human thinking.
  • constructor, method dispatcher - can be same or different.

What I have shown is a sample implementation.

What we need to standardise on is

Object representation

Once we standardise, we can ensure smooth glueing. To start with we can assume an object should be an array with at least one key called "handler" or "_handler". By this way, we support typed objects. The handler should be a proc to do method dispatching. Whether it does method dispatching within itself or between many procs is an implementation detail. By this we can support different type implementation/class implementation transparently.

An analogy is Unix world. Files can be thought of as objects. To support typed objects and type-handlers, they standardised that if the first line has "#!<string>", then <string> represents the handler. We need the equivalent of that for Tcl objects.

NEM This is exactly what the $object message args... interface gives you. I can already make use of Itcl, XOTcl, Snit, TOOT etc etc objects in pretty much the same way. Tk widgets also use this interface, as do tDOM XML DOM trees. So there already exists a de facto standard for how to use objects created by an object system. The differences are mainly in the mechanisms that an object provides for reuse (e.g. inheritance vs. delegation) and other capabilities.

vkvalli I have used snit and not others. what I meant is glueing across languages. How easy is to define a class in C and be used in snit and vice versa. Since TCL is meant to be a glueing language and embeddable. I am not very sure of that ease of glueing, atleast by snit.

I just saw Toot on seeing your comment. Sounds interesting. I think I used array type instead of TCL's native string/list type.

NEM Well, Snit doesn't do anything about defining classes in C, that's not its purpose. XOTcl and Itcl both provide some mechanism for this, as I understand it. Snit does however do a great job at being glue, in part because it builds on the assumption that other objects can be accessed with the $object method.. syntax (which is a pretty valid assumption, as I've mentioned). This allows it to delegate methods and options to a large variety of object-like things implemented in many different ways. Several of these object-like things are indeed written in C. While your proposal is an interesting new syntax for OO, I don't understand how it makes anything easier than it already is.


RVM At the end of both vkvalli's posts on the subject there appear some cliché opinions cutting short the thought.

> While your proposal is an interesting new syntax for OO, I don't understand how it makes anything easier than it already is.

That's not about the syntax, obviously. Citing vkvalli (above on the page): " ... the difference [is] in cognitive comfort in two modes. More than cognitive comfort, there are other productivity benefits too. Say I can create another generic command "sum" that can receive a list of objects which can act on "add" command ... [and] ... for a more elaborate discussion on this, see Thoughts on OO, Natural language, human thinking".