Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

read only variables

59 views
Skip to first unread message

Cecil Westerhof

unread,
Dec 16, 2017, 6:59:06 PM12/16/17
to
Is it possible to have read only variables?

I could not find something one way or the other.

--
Cecil Westerhof
Senior Software Engineer
LinkedIn: http://www.linkedin.com/in/cecilwesterhof

h.ol...@geon.nl

unread,
Dec 16, 2017, 7:27:56 PM12/16/17
to
A simple way is to use [trace] as is explained in the second answer to this question: https://stackoverflow.com/questions/46738112/use-of-trace-add-variable-read-in-tcl/46742603#46742603

Harm

Cecil Westerhof

unread,
Dec 16, 2017, 9:44:04 PM12/16/17
to
That does not make for clear code (in my opinion). In Bash I can do
something like:
declare -r foo="Some value"

It would be nice if TCL had something like that, but if not then I
have to live with it.

Is there otherwise a protocol to signify that a variable should not be
changed? In Bash you should really write it like:
declare -r _foo="Some value"

Robert Heller

unread,
Dec 16, 2017, 10:34:20 PM12/16/17
to
At Sun, 17 Dec 2017 03:33:57 +0100 Cecil Westerhof <Ce...@decebal.nl> wrote:

>
> h.ol...@geon.nl writes:
>
> > On Sunday, December 17, 2017 at 12:59:06 AM UTC+1, Cecil Westerhof wrote:
> >> Is it possible to have read only variables?
> >>
> >> I could not find something one way or the other.
> >>
> >> --
> >> Cecil Westerhof
> >> Senior Software Engineer
> >> LinkedIn: http://www.linkedin.com/in/cecilwesterhof
> >
> > A simple way is to use [trace] as is explained in the second answer to
> > this question:
> > https://stackoverflow.com/questions/46738112/use-of-trace-add-variable-read-in-tcl/46742603#46742603
>
> That does not make for clear code (in my opinion). In Bash I can do
> something like:
> declare -r foo="Some value"
>
> It would be nice if TCL had something like that, but if not then I
> have to live with it.
>
> Is there otherwise a protocol to signify that a variable should not be
> changed? In Bash you should really write it like:
> declare -r _foo="Some value"

There isn't a built in feature. OTOH, you could write a procedure that in
fact implements this, using [trace].

proc declare {varname value args} {
upvar $varname var
set var $value
if {[lsearch -exact -- $args -r] >= 0} {
trace add variable $varname {write} [list _readOnlyError $value]
}
}

proc _readOnlyError {origval varname args} {
upvar #0 $varname var
set var $origval
error "Attempt to modify $varname"
}

Then:

declare _foo "Some value" -r

will do the same as the bash command

declare -r _foo="Some value"

>

--
Robert Heller -- 978-544-6933
Deepwoods Software -- Custom Software Services
http://www.deepsoft.com/ -- Linux Administration Services
hel...@deepsoft.com -- Webhosting Services

Cecil Westerhof

unread,
Dec 16, 2017, 11:28:05 PM12/16/17
to
The -- gives:
bad option "--": must be -all, -ascii, -bisect, -decreasing, -dictionary, -exact, -glob, -increasing, -index, -inline, -integer, -nocase, -not, -real, -regexp, -sorted, -start, or -subindices
while evaluating {declare _foo "Some value" -r}

So I removed it.


> trace add variable $varname {write} [list _readOnlyError $value]
> }
> }
>
> proc _readOnlyError {origval varname args} {
> upvar #0 $varname var
> set var $origval
> error "Attempt to modify $varname"
> }
>
> Then:
>
> declare _foo "Some value" -r
>
> will do the same as the bash command
>
> declare -r _foo="Some value"

I just can overwrite the value:
$ declare _foo "Some value" -r
$ puts ${_foo}
Some value
$ set _foo dummy
dummy
$ puts ${_foo}
dummy

Cecil Westerhof

unread,
Dec 16, 2017, 11:59:04 PM12/16/17
to
And $varname needs to be $var.

In this way the value cannot be changed. But it should give an error.
But that is something that I probably can manage. ;-)

Cecil Westerhof

unread,
Dec 17, 2017, 12:59:05 AM12/17/17
to
In combination with:
http://wiki.tcl.tk/398

I came at the following function:
proc readonly {varname value} {
upvar ${varname} var
set var ${value}
trace add variable var {unset write} "error \"${varname} is readonly\";"
}

Unsetting of a variable should also not be allowed.


But when using:
readonly ROfoo SomeValue
puts "Before unset: ${ROfoo}"
unset ROfoo
readonly ROfoo SomeValue
puts "Before set: ${ROfoo}"
set ROfoo CHANGED

I get:
Before unset: SomeValue
Before set: SomeValue
ROfoo is readonly
while executing
"error "ROfoo is readonly""
(write trace on "ROfoo")
invoked from within
"set ROfoo CHANGED"

So I am on the right way, but why does unset not trigger an error?

Rich

unread,
Dec 17, 2017, 1:53:22 AM12/17/17
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> So I am on the right way, but why does unset not trigger an error?

Because of this:
trace add variable name ops commandPrefix
^^^^^^^^^^^^^
and this:
Any errors in unset traces are ignored.

part of the trace documentation.

You are passing a "script" to your trace invocations. They need to be
commandPrefix'es instead (i.e. they need to call into proc's that take
a set of arguments).

Compare:

$ rlwrap tclsh
% set x test
test
% trace add variable x unset {puts UNSET}
% unset x
% set x test
test
% proc cmdprefix {args} {puts "UNSET args='$args'"}
% trace add variable x unset [list cmdprefix hello from the trace]
% unset x
UNSET args='hello from the trace x {} unset'
%

Gerald Lester

unread,
Dec 17, 2017, 1:54:34 AM12/17/17
to
On 12/16/2017 05:39 PM, Cecil Westerhof wrote:
> Is it possible to have read only variables?
>
> I could not find something one way or the other.
>

You may hack somethings -- as others have pointed out.

That being said, Tcl does not have the concept of "declaring" a
variable, must less declaring one of a certain type or "read only".

Tcl only has the syntax rules listed in http://nikit.tcl.tk/page/Dodekalogue

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

Cecil Westerhof

unread,
Dec 17, 2017, 3:14:06 AM12/17/17
to
Rich <ri...@example.invalid> writes:

> Cecil Westerhof <Ce...@decebal.nl> wrote:
>> So I am on the right way, but why does unset not trigger an error?
>
> Because of this:
> trace add variable name ops commandPrefix
> ^^^^^^^^^^^^^
> and this:
> Any errors in unset traces are ignored.
>
> part of the trace documentation.

So it is impossible to generate an error? Also when using an
commandPrefix?


> You are passing a "script" to your trace invocations. They need to be
> commandPrefix'es instead (i.e. they need to call into proc's that take
> a set of arguments).
>
> Compare:
>
> $ rlwrap tclsh
> % set x test
> test
> % trace add variable x unset {puts UNSET}
> % unset x
> % set x test
> test
> % proc cmdprefix {args} {puts "UNSET args='$args'"}
> % trace add variable x unset [list cmdprefix hello from the trace]
> % unset x
> UNSET args='hello from the trace x {} unset'
> %

I tried:
proc readonly {varname value} {
proc cmdprefix {args} {
puts "UNSET args='$args'"
error "Variable is readonly"
}

upvar ${varname} var
set var ${value}
trace add variable var write "error \"${varname} is readonly\";"
trace add variable var unset [list cmdprefix]
}

But this only results in:
UNSET args='ROfoo {} unset'


The following does work:
proc readonly {varname value} {
proc cmdprefix {args} {
puts "[lindex $args 0] is readonly"
exec [exit 1]
}

upvar ${varname} var
set var ${value}
trace add variable var write "error \"${varname} is readonly\";#"
trace add variable var unset [list cmdprefix]
}

It is a nasty hack, but I think that it is better as not cancelling on
an unset.

And combined this gives:
proc readonly {varname value} {
proc cmdprefix {args} {
set message "[lindex ${args} 0] is readonly"
set type [lindex ${args} 2]
if {${type} eq "unset"} {
puts "${message} and cannot be unset"
exec [exit 1]
}
error ${message}
}

upvar ${varname} var
set var ${value}
trace add variable var {unset write} [list cmdprefix]

Cecil Westerhof

unread,
Dec 17, 2017, 4:28:06 AM12/17/17
to
Cecil Westerhof <Ce...@decebal.nl> writes:

> And combined this gives:
> proc readonly {varname value} {
> proc cmdprefix {args} {
> set message "[lindex ${args} 0] is readonly"
> set type [lindex ${args} 2]
> if {${type} eq "unset"} {
> puts "${message} and cannot be unset"
> exec [exit 1]
> }
> error ${message}
> }
>
> upvar ${varname} var
> set var ${value}
> trace add variable var {unset write} [list cmdprefix]
> }

Still another change:
proc readonly {varname value} {
proc cmdprefix {args} {
set message "[lindex ${args} 0] is readonly"
set type [lindex ${args} 2]
if {${type} eq "unset"} {
puts "${message} and cannot be unset"
if {!${::tcl_interactive}} {
exec [exit 1]
}
} else {
error ${message}
}
}

upvar ${varname} var
set var ${value}
trace add variable var {unset write} [list cmdprefix]
}

When in interactive mode you only want the notification and not exit
the shell. (Just like with error.)

Rich

unread,
Dec 17, 2017, 9:22:31 AM12/17/17
to
Cecil Westerhof <Ce...@decebal.nl> wrote:
> Rich <ri...@example.invalid> writes:
>
>> Cecil Westerhof <Ce...@decebal.nl> wrote:
>>> So I am on the right way, but why does unset not trigger an error?
>>
>> Because of this:
>> trace add variable name ops commandPrefix
>> ^^^^^^^^^^^^^
>> and this:
>> Any errors in unset traces are ignored.
>>
>> part of the trace documentation.
>
> So it is impossible to generate an error? Also when using an
> commandPrefix?

The answer here appears to be "yes".

Ralf Fassel

unread,
Dec 21, 2017, 4:23:46 AM12/21/17
to
* Cecil Westerhof <Ce...@decebal.nl>
| Still another change:
| proc readonly {varname value} {
| proc cmdprefix {args} {
| set message "[lindex ${args} 0] is readonly"
| set type [lindex ${args} 2]
| if {${type} eq "unset"} {
| puts "${message} and cannot be unset"
| if {!${::tcl_interactive}} {
| exec [exit 1]
| }
| } else {
| error ${message}
| }
| }
>
| upvar ${varname} var
| set var ${value}
| trace add variable var {unset write} [list cmdprefix]
| }

That looks much like shotgun programming.
Defining a proc inside a proc is at least 'strange', and the
exec [exit 1]
really does not sound like you know what you're doing.

Why not start with the trace(n) manpage and *believe* whats written in
there? I.e. why are you using 'args' in the callback procedure? The
additional arguments to the callback are listed in the trace(n) manpage,
there will not be an arbitrary set of arguments which would require 'args'.

My EUR 0.01
R'
0 new messages