Emacs Lisp Weirdness: Local Variable Bug or Feature?

  • Thread starter Thread starter Xezlec
  • Start date Start date
AI Thread Summary
In Emacs Lisp, the function defined causes unexpected behavior due to the use of `setcar` on a list initialized with a quoted empty string. When the function `annoy` is called, it appends "dolphin" to the first element of the list, which is a reference to a static object, leading to cumulative results on subsequent calls. The discussion highlights that `let` does create a local variable, but the issue arises from mutating a static object rather than creating a new instance. This behavior is compared to similar issues in C and Python, where mutable defaults can lead to unintended side effects. To avoid this, it is suggested to use `copy-sequence` to ensure a new instance is created each time. Additionally, the conversation touches on the implications of using functions like `setcar`, which break the functional programming paradigm by introducing side effects, and the preference for more straightforward coding styles that may prioritize convenience over purity.
Xezlec
Messages
318
Reaction score
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
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.
 
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?
 
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.
 
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.
 
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:
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).
 

Similar threads

Replies
13
Views
3K
Replies
7
Views
3K
Replies
7
Views
4K
Replies
1
Views
3K
Back
Top