Emacs Lisp Weirdness: Local Variable Bug or Feature?

  • Thread starter Xezlec
  • Start date
In summary, the conversation discusses the behavior of the "let" function in Emacs Lisp and how it can lead to unexpected results when combined with functions that mutate their arguments. It is suggested to use "copy-sequence" to avoid this issue and to avoid using functions with "set" in their name as they may have side effects. The conversation also touches on the concept of pure functional programming and its relevance to this issue.
  • #1
Xezlec
318
0
First off, I'm using Emacs 22.1.1 on SuSE 10.3 64-bit.

Now, the thing I've discovered is that in Emacs Lisp, if I do a C-x C-e on this:

Code:
(defun annoy ()
  (let ((glorp '("")))
    (setcar glorp (concat (car glorp) "dolphin"))
    (car glorp)))

and then on (annoy), I get "dolphin", as expected. But then if I do (annoy) again, I get "dolphindolphin" and so on. It keeps building up. Apparently the (let ((glorp '("")))) doesn't clear out glorp, and fails to make it truly local? Or (possibly more likely), that sets glorp to actually point to the list in the code right there, so changing it changes the actual damn code in the function? This makes me upset. Does anyone know what's going on here, and whether this is considered a bug or by design, and (most importantly) how to fix it?

Thanks!
 
Technology news on Phys.org
  • #2
It sounds like correct behavior, and I've seen it in other languages too. You've constructed a list containing a string, and then wrote a function whose job is to take the first element of that list, append "dolphin", and put the result back into the original list.

You may have intended the construction of a brand new list each time, but you never actually told the program to do that.
 
  • #3
Hurkyl said:
It sounds like correct behavior, and I've seen it in other languages too. You've constructed a list containing a string, and then wrote a function whose job is to take the first element of that list, append "dolphin", and put the result back into the original list.

You may have intended the construction of a brand new list each time, but you never actually told the program to do that.

So you're saying that "let" doesn't create and initialize a local variable? Or that it somehow stores every time it has been used before in a function and only uses the initializer you provided if that particular "let" has never been called before, otherwise using the value it was last set to in that context? Is there something about scoping in ELisp that I'm failing to understand?

Ok, so if "let" doesn't create and set a local variable, then what is the function to do that?
 
  • #4
Xezlec said:
So you're saying that "let" doesn't create and initialize a local variable?
As far as I can tell, this is exactly what let does. The problem is that you have instructed it to initialize your local variable to be a reference to a static object and using functions (setcar) that mutate their arguments. (Thus modifying the static object)


There's a similar situation in old C code that would look like this:

char* foo()
{
char *ptr = "abcde";
++ptr[0];
return ptr;
}

Repeatedly calling this function would return the strings "bbcde", "cbcde", "dbcde", and so forth. (among other odd behavior) These days, this is illegal C code (although it might still compile), because you're not allowed to mutate string constants. In particular, you really ought not be assigning "abcde" to a nonconstant char pointer.


It might be the case that you're actually violating the emacs lisp standard by making glorp a reference to a static object, and then mutating that object through setcar -- effectively, you're writing code like

(setcar '("") "dolphin")

which is quite silly, unless you intended for '("") to denote a static object.
 
  • #5
Hurkyl said:
It might be the case that you're actually violating the emacs lisp standard by making glorp a reference to a static object, and then mutating that object through setcar -- effectively, you're writing code like

(setcar '("") "dolphin")

which is quite silly, unless you intended for '("") to denote a static object.

Yikes. So the quote mark doesn't return a copy of the object that comes after it, it actually returns a reference to that immutable object?

Well that sucks.

So I guess the right thing to do is to replace every occurrence of '(...) in my program with (copy-sequence '(...)) or something? I certainly hope none of the library functions return references to immutable objects, since writing to constants seems to be a nasty error not caught by the interpreter.
 
  • #6
It's still possible that the observed behavior is intentional and non-buggy (again, I'm operating from a lack of familiarity with lisp semantics) -- it would be akin to the behavior of a static variable in C. I've seen a similar behavior encouraged in python, something like:

Code:
def my_function(argument, cache = {}):
   if argument in cache:
      return cache[argument]
   result = perform_lengthy_computation(argument)
   cache[argument] = result
   return result

In this case, the {} declares an empty associative array, and is only created once, and behaves somewhat like what you're seeing with glorp. Hopefully you're seeing how this feature is being put to good use.



So I guess the right thing to do is to replace every occurrence of '(...) in my program with (copy-sequence '(...)) or something?
I can't say what proper lisp style is. But I do observe that setcar breaks the pure functional programming paradigm -- it's a function call that has side-effects (it actually mutates its arguments). My suspicion is that it's probably better to avoid using that. If you can't give up its convenience, you can always make a pure function (hopefully I have the syntax right):

Code:
(defun puresetcar (x y) (cons x (cdr y)))

Though you should probably remove 'set' from the name (i.e. something like "changehead"): looking through the emacs lisp library, names with 'set' in them tend to be functions that mutate their arguments.
 
Last edited:
  • #7
Hurkyl said:
I can't say what proper lisp style is. But I do observe that setcar breaks the pure functional programming paradigm -- it's a function call that has side-effects (it actually mutates its arguments). My suspicion is that it's probably better to avoid using that.

Sorry, I'm just not a big devotee of purity. I'm using setcar to pass information back through arguments to a function rather than listing them all up and returning them, because it's more convenient this way for what I'm doing. I know how to write purely functional code, I just see little reason to do so, aside from a sort of little "look what I did" thrill.

Recursion may or may not be the most mathematically natural way to represent all concepts, but to me, loops seem to be much more natural to the human mind (at least for many things). I can just write the actual steps I would take when doing something, rather than having to sit and puzzle over the most platonic ideal form of the algorithm. Also makes it easier to make changes and identify bugs (just by comparing the code to the steps in my head).
 

1. What is Emacs Lisp and why is it considered to have "weirdness"?

Emacs Lisp is a dialect of the programming language Lisp, specifically designed for the Emacs text editor. It is known for its powerful customization capabilities and has been used for decades by programmers and text editor enthusiasts. However, its syntax and behavior can be considered unconventional and unpredictable compared to other programming languages, hence the term "weirdness".

2. What is the "local variable bug" in Emacs Lisp?

The "local variable bug" refers to a common issue in Emacs Lisp where variables declared as local within a function can sometimes persist and affect other functions. This can lead to unexpected behavior and can be challenging to troubleshoot for programmers.

3. Is the "local variable bug" a bug or a deliberate feature in Emacs Lisp?

This is a highly debated topic among Emacs Lisp enthusiasts. Some argue that it is a bug that should be fixed, while others argue that it is a deliberate feature that allows for more flexibility and creativity in programming. Ultimately, it is up to the individual programmer to decide how they want to handle this aspect of Emacs Lisp.

4. How can I avoid encountering the "local variable bug" in my Emacs Lisp code?

To avoid the "local variable bug", it is recommended to always define variables as global or local explicitly and to use the special form `let` to declare local variables within a function. It is also important to thoroughly test and debug your code to catch any unexpected variable interactions.

5. Are there any other known "weirdness" in Emacs Lisp that programmers should be aware of?

Yes, there are several other quirks and unconventional behaviors in Emacs Lisp that programmers should be familiar with. These include the use of prefix notation, the default behavior of `nil` and `t` values, and the dynamic binding of variables. It is important for programmers to thoroughly read the documentation and familiarize themselves with these behaviors to avoid any unexpected results in their code.

Similar threads

  • Special and General Relativity
Replies
13
Views
2K
  • MATLAB, Maple, Mathematica, LaTeX
Replies
7
Views
2K
  • MATLAB, Maple, Mathematica, LaTeX
Replies
7
Views
3K
  • MATLAB, Maple, Mathematica, LaTeX
Replies
1
Views
2K
Back
Top