help-gnu-emacs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

let, let*, oh, why [was: Elisp - Function returning a list]


From: tomas
Subject: let, let*, oh, why [was: Elisp - Function returning a list]
Date: Wed, 16 Dec 2020 12:52:42 +0100
User-agent: Mutt/1.5.21 (2010-09-15)

On Wed, Dec 16, 2020 at 08:36:03AM +0100, steve-humphreys@gmx.com wrote:
> > From: "Emanuel Berg via Users list for the GNU Emacs text editor" 
> > <help-gnu-emacs@gnu.org>

[...]

> > It is better to do all computation in the `let's, then use
> > them. No `setq' needed.

I generally agree, but not 100% (all generalizations suck, you know ;-)

> Example? Is there anything written about it?

What setq does is to "search upwards" from where it is until it
"finds the variable", and then sets its value as instructed.

If it doesn't find that variable, it creates a new one at the
"top level", to be able to accomplish this task.

I put "search upwards" and "find the variable" in quotes, because
they deserve some explanation. I'll limit myself here to the
"upwards" part:

What "upwards" means depends on whether you are under dynamic
scope (traditional) or lexical scope (more modern, recommended
almost always).

Under dynamic scope, the search for the variable goes up the call
chain: if the function where the setq is in "sees" that variable,
then it's there. Otherwise it asks its caller, and so on.

Under lexical scope, the search considers the source code: if
that variable is visible/defined in the current expression (think
"block", if you're coming from C/Perl/Python/PHP/Java), then that's
it. Otherwise go look in the enclosing expression.

Needless to say, this is the source of lots of fun: if you are
doing something in your code and import a snippet of code from
elsewhere (for the lexical case) or just call some code elsewhere,
and "they" trample on your "variables", spooky things happen.

In the dynamic case, those things are very spooky. Imagine
you have some code:

  (setq x 3)
  (setq y 4)

and call on Tomas's library to get the last mouse click coordinates.

  (setq mouse-x (car (tomas-coords)))

Jane's library does this (CAVEAT: there are nicer ways to do this,
apart from the "obvious" error that this function tramples over
whatever values mouse-pos, x, and y might have "globally".

I'm doing that to have a small working example. I repeat: DON'T DO
IT THIS WAY ;-)

  (defun tomas-coords ()
    (setq mouse-pos (mouse-position)) ; elisp manual 29.16
    ;; mouse-position has as first arg the frame. Let's get rid of that:
    (setq x (cadr mouse-pos))
    (setq y (cddr mouse-pos))
    (cons x y)) ;; return the pair (x . y)

Now your 'x' above isn't 3 anymore, but has some random value depending
on where your user was wiggling around the mouse. Oops.

Now under lexical scope things aren't so bleak, but still annoying.

That's why function arguments and let are there. They create a
kind of barrier for private variable names. A slightly better
version of the above function would then be:

  (defun tomas-coords ()
    (let ((mouse-pos (mouse-position))) ; elisp manual 29.16
      ;; mouse-position has as first arg the frame. Let's get rid of that:
      (let ((x (cadr mouse-pos))
            (y (cddr mouse-pos)))
        (cons x y)))) ;; return the pair (x . y)
      
So the `let' is telling elisp: "look, we are introducing here a
new variable called `x'. It has nothing to do with any other `x'
out/up there". Or "Any Similarity to Persons Living or Dead is
Purely Coincidental", as some like to put it. Still, the "here"
in the phrase above is interpreted differently depending on
whether we're "flying" dynamically or lexically.

Why the two nested `let's above, you ask? Well, in the variable-
assignment part of a let each arm is independent of the others.

The line above ...(y (cddr mouse-pos)) doesn't "see" the `x'
defined above it. It is as if all those parts were happening
at the same time. So for the `x' and `y' assignments to "see"
the just assigned `mouse-pos', we must `let' that happen
before.

That's why `let*' has been invented. Basically,

  (let* ((foo blah)
         (bar bleh)
         (baz mih))
    ...)

is equivalent, but arguably more readable than

  (let ((foo blah))
     (let ((bar bleh))
       (let ((baz mih))
         ...)))

HTH
 - t

Attachment: signature.asc
Description: Digital signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]