How to mutate global variable passed to and mutated inside function?
Asked Answered
V

5

4

I'm wondering how to permanently alter the value of a global variable from inside a function, without using the variable's name inside the function, i.e.:

(defvar *test1* 5)
(defun inctest (x) (incf x))
(inctest *test1*) ;after it runs, *test1* is still 5, not 6

According to this:

if the object passed to a function is mutable and you change it in the function, the changes will be visible to the caller since both the caller and the callee will be referencing the same object.

Is that not what I'm doing above?

Vaishnava answered 24/10, 2013 at 7:52 Comment(14)
Some objects are passed by value (actually, only numbers iirc). So what you quote is generally true, except for numbers. If you want to pass numbers by reference, you'd need some container, a cons cell would be the most basic container. You'd probably also want to cache some commonly used numbers if you are going to use this often. Another way to pass numbers by reference would be to use CFFI with pointers. From another perspective numbers are never mutable, so this quote wouldn't apply here.Hazelhazelnut
Characters may also be passed by value.Guayule
Everything is passed by value. There is no case where this: (defun foo (x) (setf x 'foo)) would change a variable that it was called with. @OpenLearner's confusion is about the difference between mutating a variable and mutating an object.Tobias
@Rörd I have accepted an answer that shows the proper way to do what I was attempting to do.Vaishnava
@OpenLearner: Yes, the answer you accepted shows the way to do what you asked in the headline. What you asked in the body of your question was actually slightly different, and I added another answer for the benefit of people who might come across this and would actually want to know the answer to what you described there.Tobias
@Rörd there are two different ways in which "passed by value" can be used. One: "by value" vs "by reference" and two: "by value" vs "by name". These are two different values, so to speak. If it is by value vs by reference, then some objects in Lisp are implemented as data embedded in the pointer (numbers and characters, which are also numbers technically). If it is by value vs by name, then it is as you say, everything is passed by value.Hazelhazelnut
@wvxvw: No, that's not pass-by-reference. This C++ function uses pass-by-reference: void foo(string& x) { x = "foo"; }, and it will change the variable of the caller. In Common Lisp, this applies: lispworks.com/documentation/lw50/CLHS/Body/03_ac.htm. Note in the first sentence: "the corresponding value".Tobias
@Rörd well, even more confusion then. The distinction of "by value" vs "by reference", the one I referred to, is the one found in managed languages, such as Java or C# (or Lisp). Where you never deal with raw values, only with pointers. In this languages, when you say "pass by reference" or "reference type", you don't mean the same thing as references in C++. Lhs can't be references in Java, only rhs.Hazelhazelnut
Here is a typical use of this idiom: javaworld.com/javaqa/2000-05/03-qa-0526-pass.htmlHazelhazelnut
@wvxvw: Yes, even more confusion. That's why I prefer unambiguous definitions over overloading terms. The Wikipedia article uses the term "call by sharing" for what you're describing: en.wikipedia.org/wiki/Evaluation_strategy.Tobias
possible duplicate of How do I globally change a variable value within function in lispAvery
I see that you've already accepted an answer, but an almost identical question was asked on Oct 21 (just three days ago), and it's already the first Google hit for "globally change value lisp". The three hits after that are also about a similar topic.Avery
@Rörd "There is no case where this: (defun foo (x) (setf x 'foo)) would change a variable that it was called with." The point that you're making is right, but I disagree with this phrasing, only because the function named by foo is never called with a variable; it's called with an object. In (foo a), because foo is a function, a` is _evaluated and its value is passed to foo. setf, on the other hand, because it is a macro can be called with variables (and, in general, places), and so can modify a variable (i.e., the value of the binding).Avery
@Rörd All that said, I understand the point you're making, and don't have any impression that you misunderstand the concept; I just wanted to clarify for the sake of others who might come across this. I discussed places and generalized reference in more detail in [my answer to the other question]( I discussed places and generalized reference in my answer to the other question of which this one, in my opinion, is a duplicate.Avery
G
3

If you want inctest to be a function, pass it the name of the global variable.

(defun inctest (x) (incf (symbol-value x)))
(inctest '*test1*)
Guayule answered 24/10, 2013 at 8:23 Comment(2)
An interesting coincidence, while doing some research for the comment thread to @6502's answer, I came across this implementation of locatives which nicely references you (I believe): "Lars Brinkhof [sic] and Paul Foley have told me how to implement locatives - things that can point to any kind of place, just like a C-style pointer. Their code made me run away and hide under the bed. This is my attempt to sneak up on the code, and overcome my fear of get-setf-expansion." :)Avery
Right. I wasn't going to toot my own horn, but here's my code: permalink.gmane.org/gmane.lisp.sources.code/17Guayule
M
2

You are not doing what the quote says, i.e. mutating the object passed to your function. You are mutating the parameter x, i.e. a local variable of your function that holds a copy of the object.

To do what the quote says, you need an object that is actually mutable which is not the case for a number. If you use a mutable object like e.g. a list, it works:

(defvar *test2* (list 5))
(defun inctest2 (x) (incf (car x)))
(inctest2 *test2*)
*test2* ; => (6)
Mick answered 24/10, 2013 at 8:53 Comment(0)
P
1

In portable Common Lisp there is no explicit concept of "pointer" and all parameters are passed by value. To be able pass a function the ability to modify a variable you need to provide a way to reach the variable.

If the variable is a global then you can use the name of the variable (a symbol) and then use symbol-value to read/write the variable:

(defvar *test1* 42)

(defun inctest (varname)
  (incf (symbol-value varname)))

(inctest '*test1*) ;; Note we're passing the NAME of the variable

if instead the variable is local you could for example provide a closure that when called without parameters returns the current value and that when called with a parameter instead it will set the new value of the variable:

(defun inctest (accessor)
  (funcall accessor (1+ (funcall accessor))))

(let ((x 42))
  (inctest (lambda (&optional (value nil value-passed))
             (if value-passed
                 (setf x value)
                 x)))
  (print x))

You can also write a small helper for building an accessor:

(defmacro accessor (name)
  (let ((value (gensym))
        (value-passed (gensym)))
  `(lambda (&optional (,value nil ,value-passed))
     (if ,value-passed
         (setf ,name ,value)
         ,name))))

after which the code becomes

 (let ((x 42))
   (inctest (accessor x))
   (print x))
Pistareen answered 24/10, 2013 at 9:11 Comment(6)
I think this question is a dupe, but I do like this answer. One thing I'd point out is that while Lisp doesn't have a concept of a pointer, it does have a concept of a place, and setf modifies the values stored in places. The real issue here is that that the place that (incf x ...) modifies is the binding of the lexical variable x. From the symbol *test* one can get an appropriate place, viz., (symbol-name '*test*). I discussed places and Generalized References in my answer to the other question.Avery
@JoshuaTaylor: places are however compile-time concepts, while pointers are run-time concepts. You can only pass a place to a macro, not to a function.Pistareen
Yes and no, I'd say. For instance, the fact that you can do (let ((varname '*test*)) (setf (symbol-name varname) new-value)) or even (setf (aref a i j k) new-value) shows that the underlying task of figuring out where to put the new value is will happen at runtime, not compile time.Avery
@JoshuaTaylor: How can you store places in a list? Both Lisp and Python lack the ability to pass/store the address of an object and you can olnly pass/store names or pathnames to the objects. The only way I know is to store addresses is using closures. In C++ instead you can have std::map<std::string, int*> that is map from names to addresses of integers and you can for example increment those integers without knowing if they're local variables, global variables or elements of an array.Pistareen
Exactly right on that count: there are no first class values corresponding to places, so you can't have, as you say, a list of them. (Although, I think there may have been some (non-Common) Lisps that had similar features. I'm thinking, perhaps, of an essay by Kent Pitman. if I find it, I'll post a link.)Avery
I misremembered the source, but the concept I was looking for was that of a locative, a first class pointer-like object for Lisp. They're briefly mentioned in an answer to Common Lisp, reference to value and actual value. Searching with Google for locatives and Lisp turns up some implementations in Common Lisp, as well as some documentation about older systems, e.g., Chapter 13. Locatives (PDF) from the Lisp Machine Manual.Avery
C
0

No, it changes a copy that you have provided.

To change the variable itself, change it in the body of the function using its name: (incf *test1*)

EDIT: in case you'd accept the macro, here it is, piping hot from my slime:

(defmacro my-incf (variable-name)  
  `(incf ,variable-name))
Combat answered 24/10, 2013 at 8:0 Comment(3)
No, the idea here is to pass an object to a function, as per the quote I've provided, and then have the function mutate it. What you propose is to directly name a global variable inside the function, and that is not the same thing as passing it to the function.Vaishnava
Then one should use the macro in this case. But it depends on your needs. Syntactically it looks as a function and maybe it would meet your requirements.Combat
Thanks but a macro shouldn't be necessary for such a simple task. And indeed the answer by Lars shows the easy way to do this.Vaishnava
P
0

This is an old question, but since I have been struggling with setf in the context of functions, local and global variables myself for some time, I'd like to give sort of an overview of what I've learned.

In (setf place newvalue) place may be one of the following according to Common Lisp the Language, 2nd Edition:

  • The NAME of a variable. or
  • A function call form whose first element is the name of any one of the following functions: aref, car ,...

What follows is a number of experiments with calling with setf inside a function or macro and the effects that has or doesn't have on an external variables that is passed to the function or macro. The goal is in all four cases to have the function or macro change the value of a global variable *y* from (1 2) to (2 2):

(defvar *y*)

; Exp. 1
(defun test1 (y)
  (setf y '(2 2)))

(setf *y* '(1 2)) ; (1 2)
(test1 *y*)       ; (1 2) because *y* is a VARIABLE and therefore not a PLACE

; Exp. 2
(defun test2 (y)
  (setf (car y) 2))

(setf *y* '(1 2)) ; (1 2)
(test2 *y*)       ; (2 2) because (car *y*) is a PLACE

; Exp. 3
(defun test3 (y)
  (setf (symbol-value y) '(2 2)))

(setf *y* '(1 2)) ; (1 2)
(test3 '*y*)      ; (2 2) because '*y* is the NAME of VARIABLE *y* and therefore a SYMBOL as well as a PLACE

; Exp. 4
(defmacro test4 (y)
  `(setf ,y '(2 2)))

(setf *y* '(1 2))  ; (1 2)
(test4 *y*)        ; (2 2) because since test4 is a macro, *y* will be evaluated in the global environment
Protoplasm answered 1/3 at 11:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.