3
votes

I'm experimenting a little with TclOO from Tcl8.6 and Rivet, but I'm in trouble, because I'm not able to do what I want.

The problem is simply reproduceable with the following code inside a .rvt file:

<?

proc dumbproc {} {
    puts "this is dumbproc ([namespace current])"
}

oo::class create foo {
    method bar {} {
        puts "this is bar ([namespace current])"
        dumbproc
    }
}

set obj [foo new]

dumbproc

$obj bar

If I simply look at the code, it seems it should work as expected, but it really doesn't because a subtle behavior of the Rivet package and the specific configuration choosen.

In this example, I'm using a .rvt file whose code is executed inside the ::request namespace, so the fully qualified name of the dumbproc procedure is ::request::dumbproc. When the name resolution algorithm is called inside the bar method, it searches for dumbproc inside ::oo::Obj12, then in ::oo, and finally in ::, without finding it and giving the following error.

this is dumbproc (::request) this is bar (::oo::Obj16)

invalid command name "dumbproc"
    while executing
"dumbproc"
    (class "::request::foo" method "bar" line 3)
    invoked from within
"$obj bar"
    (in namespace eval "::request" script line 21)
    invoked from within
"namespace eval request {
puts -nonewline ""


    proc dumbproc {} {
        puts "this is dumbproc ([namespace current])"
    }

    oo::class create..."

So, Tcl is right in doing what it does, a feature, then. But the behavior is unpredictable because when you write some class code, you must know the context it will be used in.

Indeed, I get the same error if I drop the starting <? Rivet magic, put the code inside a test.tcl file and use it in an interactive session:

$ tclsh
% namespace eval ::myns {source test.tcl}
this is dumbproc (::myns)
this is bar (::oo::Obj12)
invalid command name "dumbproc"

I tried to solve the issue by prepending the current namespace to the class creation code

::oo::class create [namespace current]::foo { ... }

then, I also tried to create the obj object inside the namespace

::oo::class create [namespace current]::foo { ... }
namespace eval [namespace current] {set obj [[namespace current]::foo new]}

then, I switched to the create method of the class for giving the object a qualified name which includes the namespace

foo create [namespace current]::obj
obj bar

but everything was unsuccessful. Every trial shows that, no matter how I do it, a method inside a TclOO class is always executed inside its object unique namespace. Am I wrong?

Is there a way to get what I want? Is TclOO not intended to work that way, and in this case why? What really surprise me is this context-dependend behavior, which I'm not sure it's the right thing, but maybe I'm completely wrong and there are sound cases for it, which I'm missing.

1

1 Answers

5
votes

The interior of each TclOO object is in reality its own namespace. You can use self namespace or namespace current inside your methods to get the name of the namespace, or info object namespace $theobj to get the namespace from anywhere. The only command placed by default in the namespace is my (for calling private methods), and some commands in other namespaces are made available through the standard Tcl namespace path mechanism (this is how you get self and next available).

The simplest way to fix this would probably be to add this to the foo class's constructor:

namespace path [list {*}[namespace path] ::request]

In your specific case, you'd have to actually add a constructor...

constructor {} {
    namespace path [list {*}[namespace path] ::request]
    # If you had a non-trivial constructor in a superclass, you'd need to call
    # [next] too.
}

Longer term, it may be reasonable to ask for a mechanism for adding to the list of namespaces that are used to make the default for objects of a class. If you want that, do submit a feature request


[EDIT]: If you're just after adding the parent namespace to the current object's command resolution path, you can do that by adding a bit more magic:

oo::class create foo {
    self {
        method create args {
            set ns [uplevel 1 {namespace current}]
            next {*}[linsert $args 1 $ns]
        }
        method new args {
            set ns [uplevel 1 {namespace current}]
            next {*}[linsert $args 0 $ns]
        }
    }
    constructor {creatorNS args} {
        namespace path [list {*}[namespace path] $creatorNS]
    }
    method bar {} {
        puts "this is bar ([namespace current])"
        dumbproc
    }
}

That will then automatically put the current namespace at creation on the path of the instance. If you're doing this in many classes, you probably want to create a metaclass with the majority of machinery in it, but the above technique (a self declaration of some methods on the foo class object itself) works fine for simple cases.