Subject: bug#66756: 30.0.50; [PATCH] Improve discussion of 'let' in Elisp Introduction manual

From: Eli Zaretskii <[email-masked]>
Date: Sat, 25 Nov 2023 09:51:48 +0200

> Date: Fri, 24 Nov 2023 13:46:55 -0800
> Cc: [email-masked], [email-masked]
> From: Jim Porter <[email-masked]>
> 
> +Another way to think about @code{let} is that it defines a special
> +region in your code: within the body of the @code{let} expression, the
> +variables you've named have their own local meaning.  Outside of the
> +@code{let} body, they have other meanings (or they may not be defined
> +at all).  In practice, this means that inside the @code{let} body,
> +calling @code{setq} for a variable named by the @code{let} expression
> +will set the value of the @emph{local} variable of that name.
> +However, outside of the @code{let} body, calling @code{setq} for a
> +variable named by the @code{let} expression will @emph{not} affect
> +that local variable.

I think something is missing from this description: "inside the 'let'
body" is ambiguous when the body calls functions.  The text should
explain that the body of functions called from 'let' is NOT considered
to be "inside the 'let' body".  This crucial point is hinted or
explained later, but it must be explained right here at the start.

> +As we discussed before, when you create local variables with
> +@code{let} under lexical binding, those variables are valid only
> +within the body of the @code{let} expression.  In other parts of your
> +code, they have other meanings, so if you call a function in the
> +@code{let} body, that function would be unable to ``see'' the local
> +variables you've created.

First, AFAIU the last sentence is correct only if the function's
definition is outside of the 'let'.  And second, this crucial
dependence on where the function is defined is very important for
understanding _why_ the function won't see the value bound by 'let'.
So it must be in the text, IMO.

> +Under dynamic binding, the rules are different: instead, when you use
> +@code{let}, the local variables you've created are valid during
> +execution of the let expression.  This means that, if your @code{let}
> +expression calls a function, that function can see (and modify) these
> +local variables.

This should say "...regardless of where the function is defined."  I
would even add that the above is true even for functions defined on
other Lisp files.

> +Another way to think about @code{let} when using dynamic binding is
> +that every variable name has a ``stack'' of bindings, and whenever you
> +use that variable's name, it refers to the binding on the top of the
> +stack.  (You can imagine this like a stack of papers on your desk with
> +the values written on them.)  When you bind a variable with
> +@code{let}, it puts the new binding you've specified on the top of the
> +stack, and then executes the @code{let} body.  Once the @code{let}
> +body finishes, it takes that binding off of the stack, revealing the
> +one it had (if any) before the @code{let} expression.

This should IMO tell that this "binding stack" is _global_, i.e. it is
seen by every function regardless of where and how it was defined.

> +Here, the result of @code{(getx)} is @code{1}.  Under lexical binding,
> +@code{getx} doesn't see the value from our @code{let} expression.
> +That's because the body of @code{getx} is outside of the body of our
> +@code{let} expression.

The last sentence is critical for understanding of the issue, and
should be at the very beginning of the 'let' description (and repeated
here, of course).

>                          Instead, @code{getx} looks for @code{x}
> +elsewhere, and finds it at the global scope of our code.

This "looks for and finds" is problematic, IMO, because it is not
clear why would it "find" the value of x set by 'setq', but not the
value of x set by 'let'.  IOW, the mechanism of "looking and finding"
remains mysterious and no intuitive description is provided to help
understanding it.  Can we provide such a description?  If you cannot
think about one, how about explaining the internal workings of this
"looking and finding" as it is implemented, and we could then take it
from there and express the idea in less technical ways.

> +Now, the result of @code{(getx)} is @code{2}!  That's because under
> +dynamic binding, when executing @code{getx}, the current binding for
> +@code{x} is the one from our @code{let} binding.  This time,
> +@code{getx} doesn't see the global value for @code{x}, since its
> +binding is below the one from our @code{let} expression in the stack
> +of bindings.

This should mention the stack and its top earlier, where it talks
about "the current binding".

Thanks.