Forwarding local namespace proc calls to instance methods in TclOO

Here is my original question:

I think I'd like to have the methods of my object appear in the command resolution path (when within a method). I'd like to write a configuration mini language where the commands of the mini language are the methods of my class. When I pass a script to one method of the object, it would eval it and have the other methods get called with out prefixing with self or my.

Where do the objects commands reside? Can they be invoked from the classes namespace in the context of the object's?

DKF: Every object has a namespace of its own, which is the current namespace whenever one of that object's methods are executing (class methods use the instance namespace). The instance namespace holds things like the variables of the object and any commands defined by the object. By default the configuration is this:

  • There is one command defined, my, which provides access to non-exported methods of the object.
  • There is one entry on the namespace path, which provides access to the next and self commands.
  • There is a variable resolver installed, but it only kicks in when inside methods (IIRC).

You can get the name of the namespace using either info object namespace, namespace current, or self namespace inside a method. Reconfigure it however you want!


Something to note is that setting up the forwarding procs must happen at instance constructor time, since the procs must be in the instance's namespace. The code which declares these procs must be called from the constructor, or the constructor itself must be modified (in a subclass of class maybe?). Here is my more refined solution:

 # Create procs in the objects namespace that forward calls to instance methods.  This
 # allows methods to be called without [self - TclOO%|%self] and [my].
 #
 proc nsproc args {
    foreach proc $args {
        proc [uplevel 1 { namespace current }]::$proc args [subst { tailcall my $proc {*}\$args }]
    }
 }

 oo::class create X {
    constructor {} {
        nsproc a b c
    }

    method a {} { b }
    method b {} { puts XXX }
 }

 X create x
 x a

My first rough answer, basically the same as above but less concise:

Here is a solution, maybe there is something easier? I need to learn to add class defining commands to help me with these wrappers I've been asking about.

 oo::class create config-language {
    variable Data

    constructor {} {
        set Data 8

        # Splice the object's "=" command to a proper invocation.  A series
        # of declarations like this would "export" the mini language to the 
        # objects namespace, where they now appear to be in the command 
        # resolution path. 
        #
        proc ::[namespace current]::= {} { my = }
    }

    method = {} { puts "fart $Data" }
    method x {} {

        # x invokes a sister method without prefix
        #
        =
    }
 }

 config-language create instance1

 instance1 x

Note that the forwarding procedure in the code above, i.e.

        proc ::[namespace current]::= {} { my = }

can also be written as an alias, i.e.

        interp alias {} [namespace current]::= {} [namespace current]::my =

In that form the interpreter does the hard work of forwarding, and there is no need to replicate the method argument list in the forwarders.

This could actually be put into a utility procedure which simply takes a list of method names to forward, or maybe a list of singles and pairs, if we wish to rename things on the fly. Lets see ...

proc ::oo::Helpers::link {args} {
    set ns [uplevel 1 {namespace current}]
    foreach link $args {
        if {[llength $link] == 2} {
            lassign $link src dst
        } else {
            lassign $link src
            set dst $src
        }
        interp alias {} ${ns}::$src {} ${ns}::my $dst
    }
    return
}

And now the setup in the constructor reduces to

    link =

Or, with a rename

    link assign =

"assign" becomes an alias of "my =".

I choose "link" as name for the helper because "forward" is already taken (actually more like that I do not remember the contexts/scopes where the builtin forward is visible, and using a different name avoids thinking about that).

DKF: Something like that is present in 8.7.