Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I globally change a variable value within function in lisp

I would like to know if there is any way to mimic C behaviour with pointers in LISP. In C if you change a value of a variable, that pointer is pointing to, it has a global effect (i.e. the value will be changed outside the function too).

So if I had

(defun mutate ( a ) 
   (some-magic-function a 5)
)

a would turn to 5 after calling mutate, no matter what it was before.

I know it is possible (much of as a side effect) with elements with lists In common-lisp, how do I modify part of a list parameter from within a function without changing the original list? but I would like to know how to do it for the entire list.

like image 252
smihael Avatar asked Sep 21 '25 12:09

smihael


1 Answers

Discussion of the C code

sds's answer address the main points of the question, but it does look like there's a little bit of confusion about what happens in the C code that you're emulating:

I would like to know if there is any way to mimic C behaviour with pointers in LISP. In C if you change a value of a variable, that pointer is pointing to, it has a global effect (i.e. the value will be changed outside the function too).

Consider the following, which I think is most closely analogous to the Lisp code that you provided:

#include<stdio.h>

int a = 3;

int mutate( int a ) {
  return a = 5;
}

int main() { 
  mutate( a );         /* or mutate( 8 ) or anything other argument */
  printf( "%d\n", a ); /* prints 3 */
  return 0;
}

The code prints three because the a in mutate is a variable that only exists within mutate. Just because it shares a name with the global a doesn't mean that changing one will change the other. The only place in this code that you can change the value of mutate's variable a is in mutate. You don't have the option of “changing [the] value of a variable that [a] pointer is pointing to”. What you can do is pass the pointer to the value of a variable, modify the value through that pointer, and then observe the results in the value afterward. This would correspond to this C code:

#include<stdio.h>

int a = 3;

int mutate( int *a ) {
  return (*a = 5);
}

int main() { 
  mutate( &a );
  printf( "%d\n", a ); /* prints 5 */
  return 0;
}

Indirection through structures

You can do things like this in Common Lisp, too, using any kind of indirection you like. For instance, if you make a a cons cell whose car is 3, then you can pass that cons around and modify the value of its car:

CL-USER> (defparameter *a* (cons 3 nil))
*A*
CL-USER> (defun mutate (cons)
           (setf (car cons) 5))
MUTATE
CL-USER> (mutate *a*)
5
CL-USER> (car *a*)
5

You don't have an address-of operator in Lisp, though, so you can't do the exactly analogue of the C code, and you'd always need to “wrap” the value somehow, if you want to use this approach. You can use the existing structures within Common Lisp such as cons cells, vectors, or anything else you can find.

Generalized References

Although it doesn't have C-style pointers, Common Lisp defines a very broad way of referring to memory locations for reading and writing, called Generalized Reference.

5.1.1 Overview of Places and Generalized Reference

A generalized reference is the use of a form, sometimes called a place, as if it were a variable that could be read and written. The value of a place is the object to which the place form evaluates. The value of a place can be changed by using setf. The concept of binding a place is not defined in Common Lisp, but an implementation is permitted to extend the language by defining this concept.

In Common Lisp, you can assign to places using setf. The suggestions that sds gave share in common that you can modify the value of a global variable either by using the global variable symbol as a place for setf, or with symbol-value. That is, after a definition such as (defparameter *a* 3) both *a* and (symbol-value '*a*) are places in which you can store a new value for *a*. As a result, I'd rather write a macro with variable names place and value, so that it's clear that any place can be used as an argument:

(defmacro mutate (place value)
  `(setf ,place ,value))

Simulating C-style pointers to variables using lexical closures

Because lexical variables are also places, there's another option that hasn't yet been considered. You can use lexical closures to create functions that will give you the same kind of capabilities as C-style pointers.

(defmacro make-pointer (place)
  `(lambda (op &optional value)
     (ecase op
       ((read)  ,place)
       ((write) (setf ,place value)))))

(let* ((x 3)
       (xp (make-pointer x)))
  (funcall xp 'write 5)             ; write a new value to x
  (list (funcall xp 'read)          ; read the value from x through xp
        x))                         ; read the value from x directly
;=> (5 5)

In this code, make-pointer returns a function that can be called with one or two arguments. The first argument should be a symbol, either read or write, and the second argument, which should provided when the first is write, is a new value to store in the place. When called with read, the value of the place is returned. When called with write, a new value is stored and returned.

There are some issues with multiple evaluation here, though. For instance, if you were to do the following, remembering that (print 2) returns the value 2:

(make-pointer (aref some-array (print 2)))

you'll end up printing 2 every time that you read or write using the pointer, which is probably not desired. I don't know whether this needs to be addressed for this question or not, but keep reading for some possible ways to avoid this.

After some research about a similar question (How to mutate global variable passed to and mutated inside function?), it's worth noting that the Lisp Machines (which ran Lisp Machine Lisp, not Common Lisp), had a concept more like C-pointers called locatives, which are briefly mentioned in the answer to Common Lisp, reference to value and actual value. Once you know the term to search for, it's easy to find out more about locatives, including Chapter 13. Locatives from the Lisp Machine Manual and various reïmplementations for Common Lisp, including Alan Crowe's which begins with a long comment that ends with a (promising) concise summary:

;;; The basic idea is to use closures

Later on (the source reads very nicely), you'll get to:

;;; It looks as though we are done
;;; now we can translate C code
;;; &x = (addr x), *x = (data x)

but there's a caveat

;;; The trouble is, we have a multiple evaluation bug.

Crowe goes on to show how get-setf-expansion can be used to create the functions that remember how to access the location and store values to it without needing to evaluate (print 2) each time. That code is certainly worth a read!

like image 153
Joshua Taylor Avatar answered Sep 23 '25 14:09

Joshua Taylor