6

I read the official documentation. Here's what not clear:

  • Scope: If I do a setf in a function, is that visible in another function.
  • Undo: How do I undo it.

in ruby, you can do

def myfunc
  a = "foo"
end

so is (setf a "foo") equivalent to a = "foo" ?

  • Whether it's visible in another function is entirely a question of the scope of the variable in question. See C-h i g (eintr)Prevent confusion and (elisp)Variable Scoping. – phils Sep 5 '18 at 21:52
  • @phils I'm not sure about that - see the example at the end of my answer. I can't really explain it very clearly. – Tyler Sep 6 '18 at 15:01
  • @Tyler See my comment about the pointer structure of conses below your answer. The effect has nothing to do with scope. So phils is right. – Tobias Sep 6 '18 at 15:40
2

The documentation for setf indicates it is a generalized version of setq, and setq has global scope. In elisp you usually use (let ...) when you want a local variable. You can see this for yourself:

(setf a "foo")
(message a) ;; => foo

(defun my-fun ()
  (setf a "bar"))

(my-fun)
(message a) ;; => bar

You can't undo this, unless you manually save the previous value somewhere so you can later restore it.

No knowing how scoping works in Ruby, I can't answer your third question.

As @tobias points out, my example was overly-simplistic; you'd normally just use setq for a case like this. The value of setf is that it lets you assign values to PLACES. That is, not only can you assign a value to the symbol tmp:

(setf tmp '("foo" "bar"))

You can also assign a value to the PLACE defined as the the car of tmp:

(setf (car tmp) "whoopie")
(print tmp) ;; ("whoopie" "bar")

There are quite a number of functions you can use as PLACES for setf. See the manual page (info "(elisp) Setting Generalized Variables") for the details.

As @phils and @tobias point out in comments, setf doesn't change Emacs' normal scoping behaviour. However, for the novice (like me in this case), it does some things that might suprise you.

When you set the value of a symbol, you're really defining a pointer. The symbol points to a memory address, and when you evaluate it Emacs gives you that value. When you set the value of a PLACE, you're actually modifying the data stored at that address, not the pointer. Which means everything that points to that address appears to have its value changed.

Here's a couple of examples that helped me sort this out. First, we can use setf to set the value of a symbol, which is exactly what setq does.

(setq tmp1 '(1))

(let ((loc1 tmp1)) ;; loc1 points to the same address as tmp1

  ;; setf updates symbol loc1 to point at a new address,
  ;; which contains the value '(2)
  (setf loc1 '(2))

  (print loc1)) ;; => '(2)

(print tmp1) ;; => '(1), nothing changed, tmp1 still points to the same 
             ;; address, and that address has its original value

Here, we set up a local variable with let, and none of the changes to that variable show up in the global context.

Now we use setf to change the PLACE (car loc1) instead of the symbol loc1:

(let ((loc1 tmp1)) ;; loc1 points to the same address as tmp1

  ;; setf updates PLACE (car loc1) to change the value at the address it
  ;; points to
  (setf (car loc1) 2)

  (print loc1)) ;; => '(2)


(print tmp1) ;; => '(2), tmp1 still points to the same address, but the 
             ;; value at that address changed!

This is the tricky bit. We modified the local variable loc1, and that change appears to have leaked out of the scope of the let to change our global variable tmp1. But it didn't really. tmp1, which is really a pointer, is the same as it always was, a pointer to an address. setf allowed us to change the value stored at that addresss, without changing the symbol itself.

  • According to the page techotopia.com/index.php/Ruby_Variable_Scope the special Ruby case with a letter as leading character of the identifier defines a local binding. That is roughly that what you get if you enclose the (setf a "bar") in my-fun into (let (bar) ...). – Tobias Sep 5 '18 at 20:26
  • 2
    Currently the example targets exactly the application case of the OP but it is not the real application case for setf. I know that you know that it does just not make sense to use setf for that case. setq is absolutely sufficient for that case. You could make the answer nicer if you add a real application case such as (setf (car a) "foo"). The question of the scope of the setting also becomes more interesting for that case, e.g., (setq l '("foo")) (let ((a l)) (setf (car a) "bar")) (message (car l)) is a nice one. – Tobias Sep 5 '18 at 20:36
  • @Tobias ok, I tried a more extended answer. It gets confusing after the first bit! – Tyler Sep 6 '18 at 15:00
  • 3
    See CAR and CDR on Wikipedia. car and cdr are pointers to data. When you (setq b l) both pointers are copied from l to b (not the data!). On the other hand (car b) refers to the data stored at the place where the first pointer points to. That is the same target for l and b. ==> (car l) and (car b) refer to the same storage place after (setq b l). Thus (setf (car b) ...) changes (car b) but that is the same as (car l). – Tobias Sep 6 '18 at 15:28
  • 1
    See also the example (progn (setq a '(1 2 3)) (setq b a) (setcar b 10) a) which is let-free and therefore is independent of the discussion about local scope. Note, that the common lisp version (progn (setq a '(1 2 3)) (setq b a) (setf (car b) 10) (print a)) also works on the common lisp repl. – Tobias Sep 6 '18 at 15:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.