Emacs Lisp weirdness?

  • Thread starter Xezlec
  • Start date
  • #1
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!
 

Answers and Replies

  • #2
Hurkyl
Staff Emeritus
Science Advisor
Gold Member
14,916
19
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
318
0
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
Hurkyl
Staff Emeritus
Science Advisor
Gold Member
14,916
19
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
318
0
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
Hurkyl
Staff Emeritus
Science Advisor
Gold Member
14,916
19
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
318
0
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).
 

Related Threads on Emacs Lisp weirdness?

  • Last Post
Replies
2
Views
2K
  • Last Post
Replies
15
Views
2K
Replies
6
Views
14K
  • Last Post
Replies
11
Views
4K
  • Last Post
Replies
0
Views
2K
  • Last Post
Replies
2
Views
2K
  • Last Post
Replies
1
Views
5K
  • Last Post
Replies
1
Views
2K
  • Last Post
Replies
1
Views
959
  • Last Post
Replies
2
Views
2K
Top