glenn jackman
glenn jackman

Reputation: 246877

TclOO: Difference between declaring variable as "class" level or in constructor

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

Answers (1)

Donal Fellows
Donal Fellows

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:

  1. In the first case, both the constructor and the twoBar method have bar actually refer to the variable ::oo::Obj12::bar (under the usual assumptions about namespace names in the TclOO implementation).
  2. In the second case, while 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

Related Questions