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

后端 未结 3 909
迷失自我
迷失自我 2020-12-19 19:46

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 e

3条回答
  •  清酒与你
    2020-12-19 20:32

    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))
    

提交回复
热议问题