[AMG]: I developed the following code so I could have multiple "instantiations" of a static-enabled [proc], where each instantiation has its own independent copies of the static variables. All instantiations share the same bytecode-compiled [proc] body. ---- ** Implementation ** ====== proc sproc {name args statics script} { upvar #0 Sproc-$name data if {[info exists data]} { error "sproc \"$name\" already exists" } set data(next) 0 set data(instances) [dict create] set data(lambda) [list $args\ "dict with [list [namespace current]::Sproc-$name](\[list\ \[namespace tail \[lindex \[info level 0\] 0\]\]\]) {$script}"] set data(statics) $statics proc $name {args} { set name [list [namespace tail [lindex [info level 0] 0]]] upvar #0 Sproc-$name data set others [info commands $name*] while {1} { set nameid $name$data(next) incr data(next) if {$nameid ni $others} { break } } dict set data(instances) $nameid "" set data($nameid) [dict replace $data(statics) {*}$args] proc $nameid {*}$data(lambda) trace add command $nameid delete [list apply {{name cmd args} { upvar #0 Sproc-$name data unset data([list [namespace tail $cmd]]) dict unset data(instances) [namespace tail $cmd] }} $name] return $nameid } trace add command $name delete [list apply {{name args} { upvar #0 Sproc-$name data foreach instance [dict keys $data(instances)] { catch {rename $instance ""} } unset data }} $name] return $name } ====== ---- ** Documentation ** *** Defining a sproc *** [[sproc ''name'' ''args'' ''statics'' ''script'']] creates a sproc called ''name'' that accepts arguments ''args'', has static variables ''statics'', and has body ''script''. It returns ''name''. * ''name'' can be any name. * ''args'' can be any [proc]-style argument list. Ordinary, defaulted, and variadic arguments are supported. * ''statics'' is a [dict] mapping from static variable names to initial values. Contrast this with defaulted arguments, in which each argument is a two-element list. * ''script'' can be any script. It does not need to do anything special in order to access or modify static variables or any other kind of variables. *** Creating an instance *** [[''name'' ?''var1'' ''val1'' ''var2'' ''val2'' ...?]], where ''name'' is a previously-defined sproc, creates an instance of the sproc with optionally overridden or augmented static variables ''var1'', ''var2'', etc. assigned to have initial values ''val1'', ''val2'', etc., respectively. It returns the name of the instance. * ''name'' must be a sproc already defined by [[sproc]], above. * ''var1'', ''var2'', etc. are the names of additional static variables to define for this instance. They can optionally be the same as names defined in the initial [[sproc]] invocation. * ''val1'', ''val2'', etc. are the initial values of the additional or overridden static variables. *** Invoking an instance *** [[''instance'' ?...?]], where ''instance'' is a name returned by the ''name'' sproc invocation, calls the sproc instance. Arguments are passed as normal. The sproc script body is able to freely access and modify static variables, which persist between subsequent invocations of the same instance. Static variables are not shared between separate instances of the same sproc. *** Deleting an instance *** Deleting ''instance'' (a sproc instance) with `[rename] ''instance'' ""` removes it from the sproc's associated data structure. *** Deleting a sproc *** Deleting ''name'' (a sproc) with `rename ''name'' ""` deletes its associated data structure and all its instances. *** Internal data structure *** Each sproc has an associated data structure named Sproc-''name'', where ''name'' is the name of the sproc. It is an [array] containing the following variables: * Sproc-''name''(next): The numeric ID of the next instance to be created. * Sproc-''name''(instances): Dictionary whose keys are the names of all current instances of the sproc. The values are all empty string. * Sproc-''name''(lambda): [Lambda] definition of an instance of the sproc. * Sproc-''name''(statics): Default dictionary mapping from static variable names to initial values. This mapping can be augmented or overridden when creating an instance of the sproc. * Sproc-''name''(''instance''): Dictionary mapping from static variable names to current values for the sproc instance named ''instance''. ---- ** Demonstration and testing ** ====== % sproc counter {{increment 1}} {value 0} {incr value $increment} counter % set p [counter] counter0 % set q [counter value 10] counter1 % $p 1 % $p 5 6 % $q 0 10 % # Show internal data structure % parray Sproc-counter Sproc-counter(counter0) = value 6 Sproc-counter(counter1) = value 10 Sproc-counter(instances) = counter0 {} counter1 {} Sproc-counter(lambda) = {{increment 1}} {dict with ::::Sproc-counter([list [namespace tail [lindex [info level 0] 0]]]) {incr value $increment}} Sproc-counter(next) = 2 Sproc-counter(statics) = value 0 % # Demonstrate instance deletion % rename $p "" % parray Sproc-counter Sproc-counter(counter1) = value 10 Sproc-counter(instances) = counter1 {} Sproc-counter(lambda) = {{increment 1}} {dict with ::::Sproc-counter([list [namespace tail [lindex [info level 0] 0]]]) {incr value $increment}} Sproc-counter(next) = 2 Sproc-counter(statics) = value 0 % # Demonstrate sproc deletion % rename counter "" % parray Sproc-counter "Sproc-counter" isn't an array % $q invalid command name "counter1" ====== ---- ** Techniques used in implementation ** [AMG]: I wanted to avoid customizing each sproc instance's script body, so I needed some other way of communicating its unique ID into the (generic) script body. Since I want this code to be easy to use, this must be done automatically. I guess I could have used [[[interp alias]]] to curry an ID argument into the invocation, but I didn't want to confuse [[[info args]]]. Instead I took advantage of the proc name being a sort of hidden parameter, which is accessed using [[[lindex] [[[info level] 0]] 0]]. In the case of the sproc (the procedure that creates instances), this gives the name of the sproc. In the case of the instances themselves, this gives the instance name, and the sproc name was already compiled into the lambda and doesn't need to be looked up dynamically. To make accessing the sproc or instance data easier, I used [[[upvar] #0]] to link a global variable to a local variable simply named "data". I didn't want to generate any procs not intended to be called by the user of this code, so I instead used [[[apply]]] to do the [[[trace]]] scripts. I did my best to facilitate bytecode sharing between all sprocs or between all instances of any given sproc. To do the former, I wrote generic code that dynamically figures out the sproc name using the trick described above. To do the latter, I generate one lambda to be used for all instances of a sproc, again dynamically figuring out the instance name. The name works somewhat like a "this" pointer in C++. I was disappointed to discover that [[info level 0]] is not fooled by [[interp alias]], that it gives the real target proc name not the alias. If not for this, I would have simply made a generic instance proc and instantiated the sproc by creating aliases. But so it turned out, I had to make a separate proc for each instance. (This is better anyway because it enables deletion through `rename ''instance'' ""`.) I wanted for the separate procs to share bytecode, so I wrote `proc $nameid [{*}]$data(lambda)`. To support deleting all instances when the sproc is deleted, I must maintain a list of instances. But I didn't want to write code to search-and-destroy through the instance list on instance deletion, so I used [[[dict unset]]] on a dict with empty string for the values. In this way I make a set using a dict. ---- ** Discussion ** [AMG]: Do all sproc instances ''really'' share the same bytecode-compiled script body? Somebody please confirm this for me. ---- [AMG]: How well does this code work with namespaces? ---- !!!!!! %| [[Category ???]] |% !!!!!!