Search code examples
propertiestclitcl

Itcl What is the read property?


I want to control read access to an Itcl public variable. I can do this for write access using something such as:

package require Itcl
itcl::class base_model_lib {
    public variable filename ""
}
itcl::configbody base_model_lib::filename {
    puts "in filename write"
    dict set d_model filename $filename
}

The configbody defines what happens when config is called: $obj configure -filename foo.txt. But how do I control what happens during the read? Imagine that I want to do more than just look up a value during the read.

I would like to stay using the standard Itcl pattern of using cget/configure to expose these to the user.

So that is my question. However, let me describe what I really want to do and you tell me if I should do something completely different :)

I like python classes. I like that I can create a variable and read/write to it from outside the instance. Later, when I want to get fancy, I'll create methods (using @property and @property.setter) to customize the read/write without the user seeing an API change. I'm trying to do the same thing here.

My sample code also suggests something else I want to do. Actually, the filename is stored internally in a dictionary. i don't want to expose that entire dictionary to the user, but I do want them to be able to change values inside that dict. So, really 'filename' is just a stub. I don't want a public variable called that. I instead want to use cget and configure to read and write a "thing", which I may chose to make a simple public variable or may wish to define a procedure for looking it up.

PS: I'm sure I could create a method which took either one or two arguments. If one, its a read and two its a write. I assumed that wasn't the way to go as I don't think you could use the cget/configure method.


Solution

  • All Itcl variables are mapped to Tcl variables in a namespace whose name is difficult to guess. This means that you can get a callback whenever you read a variable (it happens immediately before the variable is actually read) via Tcl's standard tracing mechanism; all you need to do is to create the trace in the constructor. This requires the use of itcl::scope and is best done with itcl::code $this so that we can make the callback be a private method:

    package require Itcl
    itcl::class base_model_lib {
        public variable filename ""
        constructor {} {
            trace add variable [itcl::scope filename] read [itcl::code $this readcallback]
        }
        private method readcallback {args} {         # You can ignore the arguments here
            puts "about to read the -filename"
            set filename "abc.[expr rand()]"
        }
    }
    

    All itcl::configbody does is effectively the equivalent for variable write traces, which are a bit more common, though we'd usually prefer you to set the trace directly these days as that's a more general mechanism. Demonstrating after running the above script:

    % base_model_lib foo
    foo
    % foo configure
    about to read the -filename
    {-filename {} abc.0.8870089169996832}
    % foo configure -filename
    about to read the -filename
    -filename {} abc.0.9588680136757288
    % foo cget -filename
    about to read the -filename
    abc.0.694705847974264
    

    As you can see, we're controlling exactly what is read via the standard mechanism (in this case, some randomly varying gibberish, but you can do better than that).