1
votes

I have a little problem which I cannot explain at the moment. I created a minimalistic code snippet to show my problem or lack of understanding how tcl namespaces are working.

So I have the file test.tcl:

namespace eval test {
    proc print {file_name} {
        namespace inscope ::test2 {
            printFileName $::test::file_name
        }
    }
}

namespace eval test2 {
    proc printFileName {file_name} {
        puts $file_name
    }
}

Than I use tclsh and run:

source test.tcl
test::print test.dat

Which returns:

can't read "::test::file_name": no such variable

Why is that should the argument of test::print not be in the ::test namescope? I have an easy workaround with set ::test::filename $filename before namespace inscope {}.

But I am not satisfied since I miss something here.

I cannot just run ::test2::printFileName $file_name since my real world code is more complex and does not run just one command it sources a list of commands which are all in a different namespace.

2
You did read the comment that namespace inscope isn't intended for direct usage by applications? What are you really trying to do? The trivial way to do this would be to use a proc and just pass the value instead of running code in a naked namespace. - schlenk

2 Answers

2
votes

Local variables are not namespace variables. In particular, even with variable linking (the mechanism underlying upvar and global and variable etc.) formal parameter variables are never namespace variables in any way. That's just how the variables are mapped, because it's very fast to do; the procedure entry code is one of the hottest parts of Tcl's implementation code, so great efforts are taken to keep it as fast as possible.

But all is not lost!

You can copy the value to a namespace variable easily enough, or even do tricks with traces and upvar to make it appear like the local variable can be written back to from that interior scope. (You'll probably have to use info level to search back up the stack for where to inject the write, which will be horribly messy, but it will work.)

But what I'd instead do is make commands to provide the values (and possibly allow writing back as necessary). I think it's a bit cleaner.

namespace eval test {
    proc print {file_name} {
        proc ::test2::file_name {} [list return $file_name]
        namespace eval ::test2 {
            printFileName [file_name]
        }
    }
}

namespace eval test2 {
    proc printFileName {file_name} {
        puts $file_name
    }
}

You can make this more elegant through the use of namespace path so that you don't have to build a whole new procedure each time you call. interp alias can help too (it's a good way to do argument currying, if you're familiar with that sort of thing from functional programming).

1
votes

The problem here is that ::test::file_name refers to the file_name variable in the ::test namespace and not the local variable file_name as it seems that you want. That's just the way variable name resolution works. Local variables that are arguments to commands don't reside in the enclosing namespace.

Also, it is unusual to see namespace inscope invoked directly in a script. I would write this instead:

namespace eval test {
    proc print {file_name} {
        {*}[namespace eval ::test2 namespace code printFileName] $file_name
    }
}

namespace eval test2 {
    proc printFileName {file_name} {
        puts $file_name
    }
}

Or some variation if you don't have {*} in your version of Tcl.

But your last comment about not being able to run the command directly makes me suspect that this approach may not yet solve your problem.