johnbakers
johnbakers

Reputation: 24750

Permanently mutate variable without explicit mention?

The scoping in Lisp is new to me and I think I've got it figured out, but the one area that confuses me a bit is how to mutate a global variable in a function without mentioning it specifically:

(defun empty-it (var)
  "Remove everything from var."
  (setf var nil))

Now, if I have *some-var* and call (empty-it *some-var*) it doesn't work, because the variables retains its contents from the scope prior to entering the function. Obviously, this works:

(defun empty-it-explicit ()
  "Remove everything *some-var*."
  (setf *some-var* nil))

Is it possible to have a general function that will clear the permanent contents of a stored variable, and have it work on any variable you pass to it? In other words, must you always explicitly mention a variable's name if you want it changed permanently?

(defun set-somevar-with-function (fn)
  "Pass *some-var* into a function and set it to the results."
  (setf *some-var* (funcall fn *some-var*)))

CL> (set-somevar-with-function #'empty-it)

Is this the correct Lisp idiom? If you had multiple vars you wanted to permanently mutate, would you have to write a separate function for each variable, each explicitly mentioning a different variable?

Upvotes: 2

Views: 206

Answers (3)

lindes
lindes

Reputation: 10260

If you're looking for idiomatic lisp, I think (though I'm far from expert at lisp) the thing you want is to simply not have your function do the emptying. It can supply an empty value, just let that be it. So instead of having:

(defun empty-it (var)
  (setf var nil))
; and then somewhere down the line calling:
(empty-it var)

You might do:

(defun empty ()
  nil)
; and then somewhere down the line, call:
(setf var (empty))

Common-lisp is not limited to being (and maybe could be said simply not to be) a functional language, but for this, you'll want to take a (more) functional approach – meaning that your function may take a value, but it doesn't modify variables, it simply returns another value.

Of course, if your goal is to have the semantic expression of "make this thing empty", you could use a macro:

(defmacro empty-it (var)
  `(setf ,var nil))
; and then, down the road, you can indeed call:
(empty-it var)

This would also be reasonably idiomatic.

Upvotes: 0

ben rudgers
ben rudgers

Reputation: 3669

Symbols can be unbound with makunbound. Then the symbol is not just empty but gone. The danger, as always with mutation, is shared structure. hyperspec:makunbound

The symbol-function value of a symbol can be unbound with fmakunbound. hyperspec:fmakunbound

? (setf (symbol-value 'b) 42)
42
? (setf (symbol-function 'b)(lambda (x)(+ x 1)))
#<Anonymous Function #x210057DB6F>
? b
42
? (b 4)
5
? (fmakunbound 'b)
B
? b
42
? (b 4)
> Error: Undefined function B called with arguments (4) .
> ...[snipped]
> :pop
? b
42
? (makunbound 'b)
B
? b
> Error: Unbound variable: B
> ...[snipped]
> :pop
?

Upvotes: 0

Joshua Taylor
Joshua Taylor

Reputation: 85843

Basics

The scoping in Lisp is new to me and I think I've got it figured out, but the one area that confuses me a bit is how to mutate a global variable in a function without mentioning it specifically.

The scoping, aside from the availability of dynamically scoped variables, isn't really much different than what's available in other languages. E.g., in C, if you do something like:

void frob( int x ) {
  x = 0;
}

int bar() { 
  int x = 3;
  frob( x );
  printf( "%d", x );
}

you'd expect to see 3 printed, not 0, because the x in frob and the x in bar are different variables. Modifying one doesn't change the value of the other. Now, in C you can take the address of a variable, and modify that variable through the pointer. You don't get pointers in Common Lisp, so you need something else if you want a layer of indirection. For lexical variables, you need to use closures for that purpose. For global variables (which are dynamically scoped), though, you can use the symbol that names the variable.

Direct modification of a variable (lexical or dynamic)

You can refer to global variables in the same way that you refer to any other variables in Common Lisp, by simply writing their name. You can modify them with setq or setf. E.g., you can do

(setq *x* 'new-value-of-x)
(setf *x* 'newer-value-of-x)

Indirect modification of a variable (dynamic only)

You can also take the symbol *x*, and use set or (setf symbol-value) to change the value:

(setf (symbol-value '*x*) 'newest-value-of-x)
(set '*x* 'newester-value-of-x)

Those cases give you some flexibility, because they mean that you can take the symbol as an argument, so you could do:

(defun set-somevar-with-function (var-name)
  (setf (symbol-value var-name)
        (funcall fn (symbol-value var-name))))

Understanding variable bindings (lexical and dynamic)

(Note: This is really just a rehashing of the C example above.) I think you understand why this bit of code that you posted doesn't work, but I want to mention a bit about it, just in case anyone with less experience comes across this question.

(defun empty-it (var)
  "Remove everything from var."
  (setf var nil))

Now, if I have *some-var* and call (empty-it *some-var*) it doesn't work, because the variables retains its contents from the scope prior to entering the function.

There's no unusual sense in which any variable is retaining or not retaining its value from one scope or another here. The evaluation model says that to evaluate (empty-it *some-var*), the system finds the function binding of empty-it and takes the value of *some-var*, let's call it x, and calls the function value of empty-it with the x. In doing that call, the variable var is bound to the value x. The call (setf var nil) modifies the variable var, and var has nothing to do with the variable *some-var*, except that for a while they happened to have the same value. Nothing here essentially depends on *some-var* being a global or dynamic variable, or on *some-var* and var having different names. You'd get the same results with another variable of the same name, e.g.:

(defun empty-it (var) 
  (setf var nil))     

(let ((var 'value))   
  (empty-it var)       
  var)                 
;=> 'value

You'd even get the same if the parameter of empty-it were called *some-var*:

(defun empty-it (*some-var*)
  (setf *some-var* nil))

(progn 
  (setf *some-var* 'value)
  (empty-it *some-var*)
  *some-var*)
;=> 'value

Beware of dynamic rebindings

Now, this will all work just fine if you're only modifying these variables, and you're never making new bindings for them. When you define a variable with defparameter or defvar, you're also globally proclaiming it special, i.e., dynamically scoped. The modifications done with set or setf are done to the most recent in scope binding of the variable. (When you modify a lexical variable, you're updating the innermost lexically enclosing binding.) This leads to results like the following:

(defparameter *x* 'first-value)         ; AA

(defun call-and-modify (function name)
  (setf (symbol-value name)
        (funcall function
                 (symbol-value name))))

(progn 
  (let ((*x* 'second-value))            ; BB
    (let ((*x* 'third-value))           ; CC
      (print *x*)                       ; third-value (binding CC)
      (call-and-modify (constantly 'fourth-value) '*x*)
      (print *x*))                      ; fourth-value (binding CC)
    (print *x*))                        ; second-value (binding BB)
  (print *x*))                          ; first-value (binding AA)

Upvotes: 4

Related Questions