0

I tried to ask this in a different question but did a poor job of it. This first block of code is from a JS example in David Flanagan's book. The main point of the example is that after getCounter() returns, the scope that is shared by the methods next and reset is private and no longer accessible by the rest of the code. In other words, n cannot be changed except through the two methods.

The second block of code is from an example from John Ousterhout's book on Tcl and refers to namespaces. I am not implying that his example had to do with scope; I'd just like to know if it can be made to do so. It works about the same as the JS example, as far as having the procedures change the value of num, but the scope is not private. The outer code can simply set counter::num 10 which cannot be done in the JS example. At least there appears no way to access n.

My question is is there a way to make the scope private in Tcl as in the JS example, apart from using an OOP Tcl library?

Thank you.

"use strict";
function getCounter () {
  var n = 0;
  return {
     next: function () { return ++n; },
     reset: function () { n = 0; }
  };
}           
var counter = getCounter();
console.log(counter.next()); // 1
console.log(counter.next()); // 2
console.log(counter.next()); // 3
counter.reset();
console.log(counter.next()); // 1
console.log(counter.next()); // 2
console.log(counter.next()); // 3
// Cannot access n from here because
// private scope.
namespace eval counter {
  variable num 0
  proc next {} {
    variable num
    return [incr num]
  }
  proc reset {} {
    variable num
    set num 0
  }
}
chan puts [counter::next]; # 1
chan puts [counter::next]; # 2
chan puts [counter::next]; # 3
counter::reset
chan puts [counter::next]; # 1
chan puts [counter::next]; # 2
chan puts [counter::next]; # 3
set counter::num 10
chan puts [counter::next]; # 11
chan puts [counter::next]; # 12
Gary
  • 2,393
  • 12
  • 31

2 Answers2

3

Not really - mainly because tcl does not really have references/pointers (everything is a value). The js mechanism you refer to is closures and you can't really have closures without references (multiple variables referring to the same object). See https://wiki.tcl-lang.org/page/Closures for a long discussion on this.

But tcl does have one mechanism that most other languages don't have that can be used to restrict access - only it won't be the kind of logic you are thinking about. Tcl has interp - the ability to create, delete, manipulate and execute entire tcl interpreters from tcl itself. There is a concept in tcl called safe interpreters (lots of articles on the subject). A safe interpreter is basically a tcl interpreter where you remove all the capabilities you don't want scripts to access.

This is possible because Tcl allows you to redefine or rename functions/procs, including built-in ones!. If you don't want scripts to access networking for example you can simply remove the socket command. In your case you can create an interp that has no set function - making it impossible to create or modify variables. Of course you yourself will need the set function for your own code to run so instead of deleting it you can just rename it to something 3rd parties won't guess like the smiley face emoji ( - which is a perfectly legal name for a proc!) or 3 NUL characters in a row (\0\0\0). Anyone trying to directly use the set command in your interp will just trigger an error.

slebetman
  • 109,858
  • 19
  • 140
  • 171
1

The following requires Tcl 8.7.

You can create private variables in a TclOO class, which gives them a real name that is "hard to predict" and prevents most tampering.

oo::class create Counter {
    private variable num

    constructor {} { my reset }

    method next {} { incr num }
    method reset {} { set num 0 }
}

Counter create counter
chan puts [counter next]; # 1
chan puts [counter next]; # 2
chan puts [counter next]; # 3
counter reset
chan puts [counter next]; # 1
chan puts [counter next]; # 2
chan puts [counter next]; # 3

# Can't do these; they don't work
# set counter::num 10
# chan puts [counter next]; # 11
# chan puts [counter next]; # 12

The mechanism is as safe dunder "private" fields in Python, but actually a touch harder to guess (it uses an internal ID that's otherwise used to track object identity across renaming and deletion). But there is a name, which means that the variable can participate in, say, vwait (usually if the object tells it to).

Another approach is to use a coroutine:

coroutine counter apply {{} {
    set num 0
    while true {
        switch [yield $num] {
            next { incr num }
            reset { set num 0 }
            stop { return }
            default { yieldto error "I won't do that..." }
        }
    }
}}

In this case, the variable is a local variable, and so shielded from most external access. (But in 8.7 you can use coroprobe to mess with it; that's designed for debugging this sort of scenario.)

If a value absolutely must be kept from user code, it should either be held in a different interpreter or in a C extension. Interpreters are not designed to have actual security boundaries within; they go at a different level. But for something as simple as a counter, it is not normally important to bother.(Using multiple interpreters makes a lot of sense when the code is outright untrusted, or in an application server scenario.)

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
  • I guess the coroutine comes closest to the javascript closure in how things work. And there is a tricky bug in it that I can't be bothered to fix; after all, I'd use an object to hold a counter... – Donal Fellows Jul 23 '22 at 07:35