reddit / emacs

How is it possible to change global variables in a let expression

pragmat1c1 · 2022-05-23 20:36
In this function I am changing a global variable in a let expression to change the behaviour of another function: (defun ugt-justify-text () "Justify with col width 70." (interactive) (let ((fill-column 70)) ;; <==== Changing global var `fill-column' (save-excursion (beginning-of-line) (set-mark (point)) (org-forward-paragraph) (fill-region ;; <==== to change behaviour of a function (region-beginning) (region-end) :justify :nonsqueeze)))) How and why is this possible? I was expecting that the function `fill-region` was not able to "see" any change in the global variable `fill-column`.
00-11 · 2022-05-23 20:39
`fill-column` is a "special" variable (it's even a user option), which means that it's dynamically bound. The binding has unlimited scope and lasts until the `let` is exited. RMS on why dynamic binding is (particularly) important for something like Emacs: * [Dynamic Binding](https://www.gnu.org/software/emacs/emacs-paper.html#SEC17) * [Formal Parameters Cannot Replace Dynamic Scope](https://www.gnu.org/software/emacs/emacs-paper.html#SEC18)
7890yuiop · 2022-05-23 20:44
Are you confused that the `fill-region` code sees your value of `70` for `fill-column`? That is 100% standard/default/expected **dynamic binding/scope** behaviour (which is very important for a great deal of Emacs functionality). Notice how useful and powerful it is to be able to manipulate the behaviour of something further down the call chain simply by binding the things you want to change for the particular scope that you require, without the code of every intermediate link in the chain explicitly passing a `fill-column` argument (among hundreds or thousands of others). In Emacs 24, the alternative of **lexical binding** was added to Emacs, and if `fill-column` had been a lexical variable, then `fill-region` wouldn't see it -- but `fill-column` is dynamic/"special" (i.e. it was declared via `defvar` or `defcustom` or similar*) and therefore dynamic scope applies. You can read all the details at: `C-h i g (elisp)Variable Scoping` ---- (*) This one is declared in `buffer.c` using a C code equivalent of `defvar-local`.
pragmat1c1 · 2022-05-23 20:49
Thank you for the clarification, that helps a lot to understand the underlying principles.
ir210 · 2022-05-24 14:54
Interesting. The argument is similar to the argument against checked exceptions in Java.
arthurno1 · 2022-05-24 16:28
> In this function I am changing a global variable in a let expression to change the behaviour of another function > (let ((fill-column 70)) ;; <==== Changing global var `fill-column' You are not changing a global variable, what you do here is creating an effect of a local copy of that variable, that shadows the global copy. If you are familiar with C/C++/Java and similar block scoped languages, it is like declaring a variable in an enclosing block: void main (){ int i = 3; // outer i { int i = 5; // inner i printf("inner i: %d\n", i); } printf("outer i: %d\n", i); } I haven't tested, sorry if there is a typo, but it should give result: inner i: 5 outer i: 3 Similar in Elisp: global (outer) fill-column is whatever you have by default in Emacs, and when you let-bind fill-column as in your let statement, you are creating "inner" fill-column variable that overshadows the "outer" one. > I was expecting that the function fill-region was not able to "see" any change in the global variable fill-column. The way you used it is the idiomatic way in Lisp to modify the value of a global variable when needed locally.
7890yuiop · 2022-05-24 22:12
> It is called lexical-binding, and since you see the effect, it means you have lexical scoping on in the buffer. `fill-column` is not a lexical variable, and I think perhaps you misinterpreted the question -- they were confused because they were *expecting* lexical binding but observing something different.
arthurno1 · 2022-05-24 23:48
> fill-column is not a lexical variable True, it is a dynamically bound. I was thinking more about the scope than the binding and effect of the shadowing global values. As a mental model, I think it is easier to think of "local variables" with lexical scope, than in terms of actual [implementation details as described in the manual](https://www.gnu.org/software/emacs/manual/html_node/elisp/Dynamic-Binding.html), at least in my opinion. I have removed the confusion from my comment. > they were confused because they were expecting lexical binding but observing something different. I'm not sure if that is the case, since even lexically defined variables are available in let environment to other functions: (defvar Y 10) (defun scope-test () (interactive) (let ((X 30) (Y 20)) (message "X: %s Y: %s" X Y)) (message "Y: %s" Y)) Result: scope-test X: 30 Y: 20 Y: 10 As seen, both dynamically bound Y and lexically bound X are available to functions in let statement's scope. I believe they were expected that fill-region function in their example use global (or rather buffer-local) value for fill-column, instead of let-bound value. I don't think that is a confusion about dynamic or lexical scope, they are probably just not so familiar how let bindings and environments work, maybe they had some other programming language as a mental model at the moment. At least that is how I understand their question: > How and why is this possible? I was expecting that the function fill-region was not able to "see" any change in the global variable fill-column.
7890yuiop · 2022-05-25 00:40
> since even lexically defined variables are available in let environment to other functions That is false (unless the functions in question are *defined* within the lexical scope in question) -- but then your example also does not involve "other functions" being able to see those lexical variables. The code of `message` is not trying to access a variable `X` or `Y`. You have simply passed the *value* of those lexical vars as arguments in a call to that function. If `X` and `Y` are lexical and the definition of `message` involved the variables `X` and `Y`, it would not see your bindings. This is more or less the definition of lexical scope. Contrast your example with what's happening in the question, which is quite different.
arthurno1 · 2022-05-25 09:23
> You have simply passed the value of those lexical vars as arguments Indeed, you are correct. I wasn't really thinking, seems like. (defun f () (let ((y (or x 3))) (message "%s" y))) (let ((x 2)) (f)) (lexical-let ((x 2)) (f)) To illustrate, the first one will print "2" since the first let creates dynamically bound x, while the second one can't see x and will complain. Thank you for the clarification!