Reputation: 246877
I'm confused about where to use variable myVar
and when it's required to use my variable myVar
If I define a class like this, there are no errors:
oo::class create Foo {
variable bar
constructor {input} {
set bar $input
}
method twoBar {} {
return [expr {2 * $bar}]
}
}
then
% set f [Foo new 42]
::oo::Obj12
% $f twoBar
84
But if I move variable bar
into the constructor:
oo::class create Foo {
constructor {input} {
variable bar
set bar $input
}
method twoBar {} {
return [expr {2 * $bar}]
}
}
then
% set f [Foo new 42]
::oo::Obj12
% $f twoBar
can't read "bar": no such variable
This is resolved by adding my variable bar
into the twoBar
method.
What's going on?
Upvotes: 2
Views: 500
Reputation: 137627
Inside the method, variable
is just the regular Tcl command that you know, and the current namespace is the instance namespace (that every object has).
At the declaration level, variable
is different. (It's a different command entirely.) It configures the variable resolver for the configured entity (the class Foo
in the example) to have a variable name that, if it is not found in the local (method) scope, will be looked up in the instance namespace; the resolver actually will do this for a list of variable names. The variable resolver applies to all methods defined by that class — but not its subclasses or superclasses (I tried it the other way; it sucked) — or instance if in an instance-defining context.
In your examples, this means that:
twoBar
method have bar
actually refer to the variable ::oo::Obj12::bar
(under the usual assumptions about namespace names in the TclOO implementation).bar
refers to ::oo::Obj12::bar
in the constructor (technically, only after the call to variable
), in the twoBar
method bar
only ever refers to a local variable that you never set. That means that reading from bar
fails (because of the standard semantics of variable reading).Calling my variable bar
has the same effect as variable bar
inside that method. The difference is that one can be promoted to be callable from outside the object, and the other can bind and set multiple variables at once; both are uncommon uses, but are fundamentally different.
At the bytecode level, the only noticeable difference between the two twoBar
methods is this (in the first version):
% tcl::unsupported::disassemble method Foo twoBar
ByteCode 0x0x7ff5a2845810, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
Source "\n return [expr {2 * $bar}]\n ..."
Cmds 2, src 38, inst 6, litObjs 1, aux 0, stkDepth 2, code/src 0.00
Proc 0x0x7ff5a2835f10, refCt 1, args 0, compiled locals 1
slot 0, scalar, resolved, "bar"
Commands 2:
1: pc 0-5, src 9-32 2: pc 0-4, src 17-31
Command 1: "return [expr {2 * $bar}]..."
Command 2: "expr {2 * $bar}..."
(0) push1 0 # "2"
(2) loadScalar1 %v0 # var "bar"
(4) mult
(5) done
The variable bar
(in slot 0) is marked as “resolved
” (which does shenanigans under the covers). They are otherwise entirely identical. The constructors have gross differences in the command sequences, so they're tricky to compare, but we can still see what is going on there too.
% tcl::unsupported::disassemble constructor Foo
ByteCode 0x0x7ff5a2845c10, refCt 1, epoch 17, interp 0x0x7ff5a2808010 (epoch 17)
Source "\n set bar $input\n ..."
Cmds 1, src 28, inst 5, litObjs 0, aux 0, stkDepth 1, code/src 0.00
Proc 0x0x7ff5a2835d90, refCt 1, args 1, compiled locals 2
slot 0, scalar, arg, "input"
slot 1, scalar, resolved, "bar"
Commands 1:
1: pc 0-3, src 9-22
Command 1: "set bar $input..."
(0) loadScalar1 %v0 # var "input"
(2) storeScalar1 %v1 # var "bar"
(4) done
Again, bar
is resolved
…
Upvotes: 4