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

3

3

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.

Finegrain answered 21/10, 2013 at 9:52 Comment(0)
P
7

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!

Payday answered 21/10, 2013 at 12:46 Comment(0)
B
4

Use a function

If you want to use a function, you have to pass the symbol itself:

(defun mutate (symbol value)
  (setf (symbol-value symbol) value))
(mutate 'foo 42)
foo
==> 42

Note that the symbol argument to mutate is quoted.

Use a macro

(defmacro mutate (symbol value)
  `(setf ,symbol ,value))
(mutate foo 42)
foo
==> 42

The symbol argument no longer has to be quoted.

Discussion

Arguments are passed by value in Lisp, which means that a function sees the value of its argument, not the place where it is stored. Thus a function invoked as (foo (! 5)), (foo 120), (foo x) sees only the number 120 and has absolutely no way of knowing whether this number is returned by a function (1st case), is a literal (2nd case) or stored in a variable (3rd case).

Macros receive the code (the whole form) which they transform and pass the new code to the compiler (or interpreter). Thus macros can determine how to modify their arguments (called places or generalized references in that case).

Blancheblanchette answered 21/10, 2013 at 11:56 Comment(2)
This does not function unfortunately: If I set c as an empty list and try to change it within a function with: (mutate c (remove-duplicates (merge 'list a b #'<))), I still get NIL (even though (print (remove-duplicates (merge 'list a b #'<))) prints the correct output)Finegrain
@smihael: Which version are you talking about? Macro? Function?Blancheblanchette
E
1

Here is a Common Lisp module which lets you "take the address" of any storage location which is considered a "place" in Lisp: not only variables, but slots of structures, or arrays and such.

The storage location whose address you take can be referenced by an arbitrarily complex expression, just like in C.

For instance, (ref (foo-accessor (cdr (aref a 4))) will create a reference to the storage location obtained by chasing the fifth element of array a, which is a cell, taking its cdr, and then applying foo-accessor to the object retrieved from there.

When you dereference the reference, it does not go through the entire chain each time, but rather directly to the memory location.

A simple use is:

(defun mutate-to-five (ptr)          |            void mutate_to_five(int *a)
  (setf (deref ptr) 5))              |            { *ptr = 5; }
                                     |
(defparameter a 42)                  |            int a = 42;
                                     |            /*...*/
(mutate-to-five (ref a))             |              mutate_to_five(&a);

Note that what we get here is a lot safer than C pointers. These pointers can never go bad. As long as a reference of this type exists to some place, then the object which holds that place will not disappear. We can safely return a reference to a lexical variable, for instance. These pointers are not cheap: dereferencing involves a function call to a closure, rather than simply chasing a memory address. The information need to access the storage location is stored in the lexical variable binding environment of a closure, and that closure has to be called, in order to execute the piece of code that performs the getting and setting.

A Lisp-like language could support something closer to real pointers as an extension. It would complicate the garbage collector. For instance, suppose you could have a pointer which is just an address directly aimed at the fifty-third element of some array (no heavy-weight lexical closure tricks involved). The garbage collector would have to trace these pointers so as not to reclaim an array which referenced in this way via an "interior" pointer. In the absence of such an extension, a Lisp garbage collector never has to worry about interior pointers, which do not occur. Objects on the heap are always referenced by pointers to their proper base address. Interior pointers mean that the garbage collector has to solve the question "which object contains this address?". The solution may involve searching a dynamic data structure such a tree. (This is the approach taken in the Linux kernel, which need to be able to map an arbitrary virtual address to a struct vma descriptor describing a virtual memory mapping which holds that address.)

Implementation:

;;;
;;; Lisp references: pointer-like place locators that can be passed around.
;;; Source: http://www.kylheku.com/cgit/lisp-snippets/plain/refs.lisp
;;; 
;;; How to use:
;;;
;;; Produce a reference which "lifts" the place designated
;;; by form P:
;;;
;;;   (ref p)
;;;
;;; Dereference a reference R to designate the original place:
;;;
;;;   (deref r)
;;;   (setf (deref r) 42) ;; store new value 42
;;;
;;; Shorthand notation instead of writing a lot of (deref)
;;; Over FORMS, A is a symbol macro which expands to 
;;; (DEREF RA), B expands to (DEREF RB):
;;;
;;;   (with-refs ((a ra) (b rb) ...)
;;;     
;;;     ... forms)
;;; 
(defstruct ref 
  (get-func) 
  (set-func))

(defun deref (ref) 
  (funcall (ref-get-func ref))) 

(defun (setf deref) (val ref) 
  (funcall (ref-set-func ref) val)) 

(defmacro ref (place-expression &environment env)
  (multiple-value-bind (temp-syms val-forms 
                        store-vars store-form access-form)
                        (get-setf-expansion place-expression env)
    (when (cdr store-vars)
      (error "REF: cannot take ref of multiple-value place"))
    `(multiple-value-bind (,@temp-syms) (values ,@val-forms)
       (make-ref
         :get-func (lambda () ,access-form)
         :set-func (lambda (,@store-vars) ,store-form)))))

(defmacro with-refs ((&rest ref-specs) &body forms) 
  `(symbol-macrolet 
     ,(loop for (var ref) in ref-specs 
            collecting (list var `(deref ,ref))) 
     ,@forms))
Ely answered 23/10, 2013 at 23:13 Comment(3)
+1 for showing a solution that uses get-setf-expansion to avoid multiple evaluation issues! -1 for not including the code. If that link goes dead, this answer doesn't help anyone anymore. :( I'd upvote if the code got into the answer. :)Payday
@JoshuaTaylor I put the code in. But you know, if every web page copies instead of linking, just in case links go dead, that destroys the concept of the world-wide web.Ely
I agree, the usefulness of the web is that it's a web. That said, Stack Overflow is about providing enduring, canonical answers to questions. Yours wasn't a "link-only" answer, but even with the additional content, it wouldn't be useful to someone who finds it here later if the link is dead. See, e.g., Are answers that just contain links elsewhere really “good answers”?. I do recognize, though, that this was not a link-only answer; there was much more to it than that.Payday

© 2022 - 2024 — McMap. All rights reserved.