スコープ

Tclは、プロシジャのスコープと変数のスコープを別々に持っています。
つまり、プロシジャ名と変数名は同名であってもよいということです。
プロシジャを別々のスコープに分離するには、namespace[incr Tcl]を使います。

プロシジャのスコープ

プロシジャは、globalスコープで定義されても、localスコープで定義されても
globalなプロシジャとなります。

変数のスコープ

すべてのプロシジャの外側にあるスコープをglobalスコープという。
プロシジャのスコープはlocalスコープになります。
プロシジャ内で定義された変数はlocalスコープの変数になります。
プロシジャ内でglobal変数にアクセスするには、globalコマンドで宣言が必要になります。

set gvar "Hello World"

puts $gvar ;# global宣言は不要

proc foo {} {
    global gvar
    puts $gvar ;# global宣言は必要
}

Tcl8.0からは、namespaceのスコープ解決演算子(::)を使って完結に書くこともできます。
この場合、global宣言が不要ですっきりします。

set gvar "Hello World"

proc foo {} {
    puts $::gvar
}

upvar

upvarコマンドは、プロシジャの呼び出し側のスコープの変数を、
現在のスコープのlocal変数へと結びつけるのに使います。
以下の例では、変数名gvarをプロシジャfooに渡して、
プロシジャfoo内で変数gvarを変更しています。

set gvar "Hello World"

proc foo {arg} {
    upvar $arg var
    set var "Good Morning"
}

foo gvar
puts $gvar

upvarの第1引数にnumberを指定すると、レベルを指定できます。
1を指定すると1つ上のスコープという意味になります。
省略時のデフォルトは1です。

また、#numberを指定すると、globalスコープからの相対レベルになります。
globalスコープは#0になるので、globalコマンドは以下と同じ意味になります。

upvar #0 foo foo

uplevel

upelvelコマンドは、upvarと似ています。
uplevelは、現在のスコープ外でコマンドの実行をする時に使います。
以下の例では、プロシジャfoo内でglobalスコープも用いてsetすることにより、
global変数gvarを直接変更しています。

set gval {Hello World}

proc foo {} {
    uplevel {set gval {Good Morning}}
}
foo
puts $gvar

uplevelの第1引数にnumberを指定すると、レベルを指定できます。
1を指定すると1つ上のスコープという意味になります。
省略時のデフォルトは1です。
また、#numberを指定すると、globalスコープからの相対レベルになります。

イベントハンドラと変数スコープ

Tclは、イベントハンドラを使ってコマンドを実行することがあります。
afterコマンド、bindコマンド、button -commandコマンド等が該当します。
イベントハンドラはglobalスコープで実行される点に注意してください。
たとえば、以下の例は、afterで1秒後にコマンドが実行されますが、
コマンドが実行されるのはglobalスコープなので、変数varは未定義エラーになります。

proc foo {} {
    set var "Hello World"
    after 1000 {puts $var}
}
foo

解決策としては、変数varをafterコマンドに渡す前に展開する方法です。

# ちょっと複雑?
proc foo {} {
    set var "Hello World"
    after 1000 "puts \"$var\""
}
foo
# ややシンプル?
proc foo {} {
    set var "Hello World"
    after 1000 [list puts $var]
}
foo

イベントハンドラでコマンドを起動するbindやbutton -command等も同様です。
特にダブルクォートを使うとコマンド置換と変数置換の結果が予測しにくくなり、
これによってスクリプトにバグが組み込まれてしまう可能性があります。
十分注意して使いましょう。