Does bind operate in a separate scope?

52 views
Skip to first unread message

Luc

unread,
Sep 22, 2022, 12:06:09 AMSep 22
to
I guess I never learned about this, and I need to:

set foo "bar"
puts "test 1 $foo"
bind $::w <Control_L><s> {puts "test 2 $foo"}

Test 1 prints "bar", but test 2 won't. An error says there is no "foo" variable.

Does bind operate in a separate scope?

Ralf Fassel

unread,
Sep 22, 2022, 4:16:00 AMSep 22
to
* Luc <luc...@gmail.com>
The code you show does not run because $::w is not defined.

You need to post a *complete* example¹ that shows the error, otherwise
anyone wanting to help needs to guess what might be wrong, and many of
the regulars skip the post right at that point.

And, my experience is that while preparing the complete example, many
times I stumble upon the solution.

In your case, most probably the code you show is inside a proc, so foo
is defined only inside that proc. As the bind manpage

https://www.tcl-lang.org/man/tcl/TkCmd/bind.htm

explains:

BINDING SCRIPTS AND SUBSTITUTIONS
[...]
Command will be executed in the same interpreter that the bind
command was executed in, and it will run at global level (only global
variables will be accessible).

HTH
R'
---
¹ http://www.catb.org/~esr/faqs/smart-questions.html#beprecise

Luc

unread,
Sep 22, 2022, 11:24:47 AMSep 22
to
On Thursday, September 22, 2022 at 5:16:00 AM UTC-3, Ralf Fassel wrote:
> * Luc
I am sorry if I wasn't sufficiently clear. I believe the entire code is too large to be posted here.

But I am prone to believing that the information I submitted is clear enough.

Let me change it just a little:

set file "/home/luc/somefile.txt"
puts "test $file"
bind $::w <Control_L><o> {proc.openfile $file}

The proc fails to open the file. It complains that the "file" variable doesn't exist.

And this works:

bind $::w <Control_L><o> {proc.openfile /home/me/somefile.txt}

Why?

What confuses me is that yes, the variable exists. The puts command right before it proves that. So in my head, {proc.openfile $file} should translate directly into {proc.openfile /home/me/somefile.txt} and that message should be received by bind and proc.openfile accordingly. That is my problem. Some shift in scope is happening here that I don't understand. I replaced the intended command with [info vars] and realized that only global variables are available, $foo is not available although it can be found and printed only one line before, and I could solve the whole problem right now by just using a global variable. But I want more than that. I want to learn. I want to know why Tcl is not doing what I thought it would.

So the documentation says, as you pointed out, that "the bind command was executed in, and it will run at global level (only global variables will be accessible)"

OK, now I know that, but I still find it confusing. Programming languages usually like to provide for safety mechanisms, the opposite of this "only globals allowed" decision. Globals tend to be seen as unsafe so I wouldn't expect them to be compulsory for passing arguments.

But OK, I guess the case is closed. Thank you for your attention.

Gerald Lester

unread,
Sep 22, 2022, 11:59:37 AMSep 22
to
Sorry, but again you are hiding stuff! The following works:


gerald@gerald-laptop:/tmp/test$ cat scope.tcl
proc proc.openfile {f} {
puts "In proc with f == {$f}"
}

set w [entry .e]
grid configure $w
set file "/home/luc/somefile.txt"
bind $::w <Control_L> {proc.openfile $file}

gerald@gerald-laptop:/tmp/test$ wish scope.tcl
In proc with f == {/home/luc/somefile.txt}

As stated in the man page -- binds are executed at the global level.
That means that only global variables are visible.

>
> And this works:
>
> bind $::w <Control_L><o> {proc.openfile /home/me/somefile.txt}
>
> Why?
>
> What confuses me is that yes, the variable exists. The puts command right before it proves that. So in my head, {proc.openfile $file} should translate directly into {proc.openfile /home/me/somefile.txt} and that message should be received by bind and proc.openfile accordingly. That is my problem. Some shift in scope is happening here that I don't understand. I replaced the intended command with [info vars] and realized that only global variables are available, $foo is not available although it can be found and printed only one line before, and I could solve the whole problem right now by just using a global variable. But I want more than that. I want to learn. I want to know why Tcl is not doing what I thought it would.
>
> So the documentation says, as you pointed out, that "the bind command was executed in, and it will run at global level (only global variables will be accessible)"
>
> OK, now I know that, but I still find it confusing. Programming languages usually like to provide for safety mechanisms, the opposite of this "only globals allowed" decision. Globals tend to be seen as unsafe so I wouldn't expect them to be compulsory for passing arguments.
>
> But OK, I guess the case is closed. Thank you for your attention.




--
+----------------------------------------------------------------------+
| Gerald W. Lester, President, KNG Consulting LLC |
| Email: Gerald...@kng-consulting.net |
+----------------------------------------------------------------------+

Ralf Fassel

unread,
Sep 22, 2022, 12:22:04 PMSep 22
to
* Luc <luc...@gmail.com>
| puts "test $file"
| bind $::w <Control_L><o> {proc.openfile $file}
--<snip-snip>--
| What confuses me is that yes, the variable exists. The puts command
| right before it proves that. So in my head, {proc.openfile $file}
| should translate directly into {proc.openfile /home/me/somefile.txt}

Read about the effect of {} on variable substitution in

https://www.tcl.tk/man/tcl/TclCmd/Tcl.html#M10

[6] Braces.
...
No substitutions are performed on the characters between the
braces
...

So in your code the $file is not substituted when the bind runs, because
of the {} surrounding it.

Rather when the bind *triggers*, the script is evaluated in global
scope, and it is _there_ that the variable does not exist.

Note: if you want to substitute $file right at that point (rather than
when the binding triggers), use list:
bind $::w <Control_L> [list proc.openfile $file]

Another note:
If the whole code is too large to show, strip it down to a minimal
example which shows the error - and in that process of stripping down
you will in many cases find the problem yourself.

HTH
R'

Robert Heller

unread,
Sep 22, 2022, 12:29:26 PMSep 22
to
At Thu, 22 Sep 2022 08:24:44 -0700 (PDT) Luc <luc...@gmail.com> wrote:

>
> On Thursday, September 22, 2022 at 5:16:00 AM UTC-3, Ralf Fassel wrote:
> > * Luc
> > | I guess I never learned about this, and I need to:
> > >
> > | set foo "bar"
> > | puts "test 1 $foo"
> > | bind $::w <Control_L><s> {puts "test 2 $foo"}
> > >
> > | Test 1 prints "bar", but test 2 won't. An error says there is no "foo" variable.
> > >
> > | Does bind operate in a separate scope?
> > The code you show does not run because $::w is not defined.
> >
> > You need to post a *complete* example¹ that shows the error, otherwise
> > anyone wanting to help needs to guess what might be wrong, and many of
> > the regulars skip the post right at that point.
> >
> > And, my experience is that while preparing the complete example, many
> > times I stumble upon the solution.
> >
> > In your case, most probably the code you show is inside a proc, so foo
> > is defined only inside that proc. As the bind manpage
> >
> > https://www.tcl-lang.org/man/tcl/TkCmd/bind.htm
> >
> > explains:
> >
> > BINDING SCRIPTS AND SUBSTITUTIONS
> > [...]
> > Command will be executed in the same interpreter that the bind
> > command was executed in, and it will run at global level (only global
> > variables will be accessible).
> >
> > HTH
> > R'
> > ---
> > ¹ http://www.catb.org/~esr/faqs/smart-questions.html#beprecise
>
> I am sorry if I wasn't sufficiently clear. I believe the entire code is too
> large to be posted here.
>
> But I am prone to believing that the information I submitted is clear enough.

It really wasn't.

>
> Let me change it just a little:
>
> set file "/home/luc/somefile.txt"
> puts "test $file"
> bind $::w <Control_L><o> {proc.openfile $file}
>
> The proc fails to open the file. It complains that the "file" variable
> doesn't exist.

That is probablly because it does not exist *in the global scope*. Question:
is the code snippet inside of a proc? If it is, it won't work.

>
> And this works:
>
> bind $::w <Control_L><o> {proc.openfile /home/me/somefile.txt}
>
> Why?
>
> What confuses me is that yes, the variable exists. The puts command right
> before it proves that. So in my head, {proc.openfile $file} should translate
> directly into {proc.openfile /home/me/somefile.txt} and that message should
> be received by bind and proc.openfile accordingly. That is my problem. Some
> shift in scope is happening here that I don't understand. I replaced the
> intended command with [info vars] and realized that only global variables
> are available, $foo is not available although it can be found and printed
> only one line before, and I could solve the whole problem right now by just
> using a global variable. But I want more than that. I want to learn. I want
> to know why Tcl is not doing what I thought it would.
>
> So the documentation says, as you pointed out, that "the bind command was
> executed in, and it will run at global level (only global variables will be
> accessible)"
>
> OK, now I know that, but I still find it confusing. Programming languages
> usually like to provide for safety mechanisms, the opposite of this "only
> globals allowed" decision. Globals tend to be seen as unsafe so I wouldn't
> expect them to be compulsory for passing arguments.

What you want to do is create some sort of "closure" type of thing (something
that "remembers" the current scope or contex) if you want to avoid using
globals -- there are a number of "hacks" to do this, including several of the
OOP frameworks that are available. Another option would be to use variables in
namespaces. Or both.

What is going on here is that the event loop itself runs in the global level.
*bind* itself never actually calls anything at any level. It just creates a
event object and adds it to the event list. The event object has a mask for
the event(s) and possibly the object the event relates to. The event loop
loops through the event list and when an element has a matching event, the
code is then passed to eval (Tcl_Eval), at the current (global) level.

>
> But OK, I guess the case is closed. Thank you for your attention.
>
>

--
Robert Heller -- Cell: 413-658-7953 GV: 978-633-5364
Deepwoods Software -- Custom Software Services
http://www.deepsoft.com/ -- Linux Administration Services
hel...@deepsoft.com -- Webhosting Services

LES

unread,
Sep 22, 2022, 2:33:49 PMSep 22
to
**************************
On Thu, 22 Sep 2022 18:21:59 +0200, Ralf Fassel wrote:

> Read about the effect of {} on variable substitution in
>
> https://www.tcl.tk/man/tcl/TclCmd/Tcl.html#M10
>
> [6] Braces.
> ...
> No substitutions are performed on the characters between the
> braces
> ...
>
> So in your code the $file is not substituted when the bind runs,
> because of the {} surrounding it.
>
> Rather when the bind *triggers*, the script is evaluated in global
> scope, and it is _there_ that the variable does not exist.
>
> Note: if you want to substitute $file right at that point (rather than
> when the binding triggers), use list:
> bind $::w <Control_L> [list proc.openfile $file]


Ah, yes! That really makes sense now. Total forehead slapping moment.
Of course I know about variable substitution, but I wasn't making the
connection with the hard brackets. I tried using list and now it works
just as expected. Double quotes works too.

Now it makes sense.

Thank you!

clt.to...@dfgh.net

unread,
Sep 22, 2022, 3:27:10 PMSep 22
to

>connection with the hard brackets. I tried using list and now it works
>just as expected. Double quotes works too.

Be careful about using double quotes.
They work until someone has a file name with a space (C:\My Data),
when it will fail. Using [list ...] is much safer.


Dave B






Robert Heller

unread,
Sep 22, 2022, 3:29:34 PMSep 22
to
Warning: Double quotes are meant for strings and might not handle "quoting"
correctly. When building a command/script to be passed off to bind, etc.
using list is always prefered, since proper quoting will occur.

>
> Now it makes sense.
>
> Thank you!
>
>
>

Ralf Fassel

unread,
Sep 22, 2022, 5:21:23 PMSep 22
to
* LES <luc...@lucianoes.info>
| > Note: if you want to substitute $file right at that point (rather than
| > when the binding triggers), use list:
| > bind $::w <Control_L> [list proc.openfile $file]
>
| Ah, yes! That really makes sense now. Total forehead slapping moment.

:-)

| I tried using list and now it works just as expected. Double quotes
| works too.

Note that double quotes will fail if your file name contains spaces.
Always use [list] to build callbacks or -command scripts.

R'

Gerald Lester

unread,
Sep 23, 2022, 8:46:34 AMSep 23
to
If the file name contains a space, double quotes will not work as you
appear to expect.
Reply all
Reply to author
Forward
0 new messages