Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: All the way up or current scope



I'll later start a separate thread to discuss support for generalized upper scope references as that is orthogonal to support for references to nested variables.

  f() {
   local -n ref
   local var=${ local X=1 Y=2; for ref in X Y; do ... done }
  }

I don't find this example very convincing because it looks too specific. Consider the following example

() {
  () { local -n ref; local A=1; ref=A; echo "ref=$ref " }
  () { local -n ref; local X=1; ref=X; echo "ref=$ref " }
}

Here too, one may want to factor out "local -n ref" but that won't work. In the first example it only works because the "for" construct is used. If that's the only reason we should support references to nested variables, it sounds like a very high price for a very specific and most likely very rare use case.

I think that a more convincing argument is what Oliver alludes to, namely that average users may simply expect that reference to nested variables work. On one hand we have advanced users that understand that they should use "typeset -nu" rather than a plain "typeset -n" to refer to variables passed by name. Expecting from these users to understand that the references are relative to the declaration scope of the reference is probably not asking too much. On the other hand, we have average users who kind of just understand the concept of named reference but have no knowledge of all the intricacies. For them, it might be surprising that initializing an encolsing referece with a nested variable doesn't work.

If that's our concern, then I guess references to nested variables should be supported.

Now, regarding all the way up, since my previous arguments couldn't convince you, here is one of your own, namely the lack of self-reference detection. 

in fact, because (with your "loose"  experiment patch) a
reference can "float" back into its own scope, it's trivially easy to
cause a reference to loop back to itself.

What example did you have in mind? I noticed that in the following example the loop is detected only if the outer ref1 is commented out.

() {
  typeset ref1=outer
  () {
    typeset -n ref2=ref1
    echo "ref1=$ref1 ref2=$ref2"
    typeset -n ref1=ref2
    echo "ref1=$ref1 ref2=$ref2"
  }
}

It's a little surprinsing to me. I expected that the second ref1 definition would detect the loop since all the information needed to do so is presnt at that point of time.

Anyway, just an aside, my point is that you can also easily create loops with the current implementation:

() {
  typeset -n ref1=ref2
  typeset -n ref2
  () {
    () {
      typeset ref1=inner;
      ref2=ref1
      echo "ref1=$ref1 ref2=$ref2"
    }
    echo "ref1=$ref1 ref2=$ref2"
  }
}

If desired, the loop created when the innermost scope is exited could be detected. It would require to lookup what ref2 newly refers to after its previous referent ceased to exit. If that is done then all the way up comes for free.

typeset -nu always being relative to declaration does seem to be the
most logical, sensible and useful behaviour. I believe this is what has
now been implemented and pushed.

Indeed, in the latest implementation "typeset -nu ref; ...; ref=var" searches for "var" in the enclosing scope of where "ref" was defined, independent of the scope where "ref=var" is called.

Fixing typeset -n to the current scope (applying the -u0 Philippe
suggested by default) would seem counterintuitive to me. With the
default of dynamic scoping, I'm used to being able to just use variables
from parent scope.
 Digging into parent scopes should always be very much
explicit.

You would still be able to use variables from the parent scope. What -u0 means is that the serach for "var" doesn't start in the scope where "ref=var" is called but in the scope where "typeref -n ref" is defined but the search still looks into enclosing scopes. The following example would print "ref=1" if -n defaulted to -nu0.

() {
  typeset var=1
  () {
    typeset -n ref
    () {
      typeset var=2
      ref=var
      echo ref=$ref
    }
  }
}

Philippe

On Tue, May 20, 2025 at 11:40 PM Oliver Kiddle <opk@xxxxxxx> wrote:
On 12 May, Bart Schaefer wrote:
> As I said earlier in this thread, we tried a pointer implementation
> and never got as far as what's now done.  It ended up requiring
> reference counting.  I believe Oliver still has the partly-finished
> code for that variation somewhere.

The main challenge with the pointer implementation was not the reference
counting but the various places in the code where zsh was causing the
pointer to be invalid such as by removing it and creating it anew when
changing a parameter's type. I did have fixes to most such cases and if
you think there's value in such refactorings, they could be resurrected.
The way that private variables are implemented also broke it and the
last time I rebased that code was sometime before they were added.
Reference counting would allow a local variable that has a reference
to persist after the scope ends which would have some interesting uses
but the current implementation enables other more useful things like
references to subscripts. While experimenting with reference counting I
repeatedly had nagging doubts about the wisdom of the approach and don't
for a moment think we should be considering it now that we have a decent
alternative implementation working.

I've not been in a position to try the latest patches and haven't
followed every word of the discussion but just to put in my thoughts on
some of the main points:

typeset -nu always being relative to declaration does seem to be the
most logical, sensible and useful behaviour. I believe this is what has
now been implemented and pushed.

Fixing typeset -n to the current scope (applying the -u0 Philippe
suggested by default) would seem counterintuitive to me. With the
default of dynamic scoping, I'm used to being able to just use variables
from parent scope. Digging into parent scopes should always be very much
explicit.

I can see the potential uses of being able to specify -u0 or -u2.
But I certainly agree with Bart that it would be better to get a new
release out sooner without more new inventions. Using numeric counts
seems somewhat ugly and you should have the effect of -u2 by creating
a -un reference to a -un reference. Only problem is there's no way
to change the second of those references given the name of the first
reference. With a reference passed to a for loop where that reference
is a reference to another reference, it could follow to the end of the
reference chain and iterate the final reference. Whether that reference
was defined with -u or not would determine the scope for each target in
the for loop.

Oliver


Messages sorted by: Reverse Date, Date, Thread, Author