Variable references in lisp
Asked Answered
A

7

28

Another newbie (Common) LISP question:

Basically in most programming languages there's a mean for functions to receive references to variables instead of just values, that is, passing by reference instead of passing by value. Let's say, for the sake of simplicity, I want to write a LISP function that receives a variable and increases the value of the variable by one:

(defun increase-by-one (var)
  (setf var (+ var 1)))

Now obviously the problem is that this function only increases the value of the copy of the variable on the stack, not the actual original variable. I've also tried to achieve the effect by using macros without much success, although I have this feeling that using macros is the right way to go.

I hit this wall all the time in LISP and I'm sure there must be a way around it or maybe there's a completely different approach to this problem in LISP I haven't thought about? How are things like this are done in LISP?

EDIT: Multiple people has suggested using incf. I only used this example to demonstrate the problem in a simple way, I wasn't actually looking for reimplementing incf. But thanks for the suggestions anyway.

Albatross answered 8/8, 2009 at 21:54 Comment(2)
You say "most programming languages" but are there languages with such feature other than C++ and Perl?Alexandrina
@LeCurious Pascal is another oneBentley
N
24

With lexical scope one does not have access to variables that are not in the current scope. You also cannot pass lexical variables to other functions directly. Lisp evaluates variables and passes the values bound to these variables. There is nothing like first-class references to variables.

Think functional!

(let ((a 1))
  (values (lambda (new-value)
            (setf a new-value)) 
          (lambda () a)))

above returns two functions. One can read the variable, another one can write the variable.

Let's call the first function writer and the second one reader.

(defun increase-by-one (writer reader)
   (funcall writer (1+ (funcall reader))))

So, to do what you want, the code needs a) be in the scope or b) have access to functions that are in the scope.

Also the variable could be global.

(defvar *counter* 1)

(defun increase-by-one (symbol)
  (set symbol (1+ (symbol-value symbol))))
  ; note the use of SET to set a symbol value

(increase-by-one '*counter*)

This works for global variables that are represented by a symbol. It does not work for lexical variables - these are not represented by a symbol.

There is also a macro INCF that increases a 'place' (for example a variable).

(incf a)

But a is the variable in the current scope.

(defun foo (a)
  (incf a))  ; increases the local variable a

The limit is seen here:

(defun foo (var)
  (add-one-some-how var))

(let ((a 1))
   (foo something-referencing-a))

There is no way to pass a direct reference of a to FOO.

The only way is to provide a function. We also have to rewrite FOO, so that it calls the provided function.

(defun foo (f)
  (funcall f 1))   ; calls the function with 1

(let ((a 1))
   (foo (lambda (n)
          (setf a (+ a n)))))
   ;; passes a function to foo that can set a
Nonaligned answered 8/8, 2009 at 23:0 Comment(0)
L
11

Of course, in Lisp you can make your own way make variable references, if you want to. The simplest approach is like this:

(defstruct reference getter setter)

(defmacro ref (place)
  (let ((new-value (gensym)))
    `(make-reference :getter (lambda () ,place)
                     :setter (lambda (,new-value)
                               (setf ,place ,new-value)))))

(defun dereference (reference)
  (funcall (reference-getter reference)))

(defun (setf dereference) (new-value reference)
  (funcall (reference-setter reference) new-value))

And then you can use it:

(defun increase-by-one (var-ref)
  (incf (dereference var-ref)))

(defun test-inc-by-one (n)
  (let ((m n))
    (increase-by-one (ref m))
    (values m n)))

(test-inc-by-one 10) => 11, 10
Lawford answered 9/8, 2009 at 5:12 Comment(0)
T
10

While Common Lisp supports a functional programming style, that is not its general focus (Scheme, while not purely functional, is much closer). Common Lisp supports a completely imperative style of programming very well.

If you find you need to write code like that, the usual solution is a macro:

(defmacro increase-by-one (var)
  `(setf ,var (+ ,var 1)))

This allows you to write code like:

(increase-by-one foo)

which will be expanded into:

(setf foo (+ foo 1))

before it is compiled.

Torrance answered 8/8, 2009 at 22:4 Comment(4)
A better solution is to use INCF, particularly because your INSCREASE-BY-ONE is buggy and evaluates VAR twice.Callum
That's a good point, my example would be a better illustration if it used setq instead of setf. The setf macro has a whole world of code behind it to support generalised place variables, so the above increase-by-one macro is inappropriate for use with setf.Torrance
@Luís Oliveira: Yeah that's why I was puzzled by a macro approach as well. Altough I can't see how evaluating a variable twice could be a problem. I mean, it's not an expression, it's just a variable anyway. Or do I miss something?Albatross
@DrJokepu: setf supports operations like (setf (car foo) bar) or even (setf (fifth list) 42). When used with the above macro example, (fifth list) would be evaluated twice: See here for more info: supelec.fr/docs/cltl/clm/node80.htmlTorrance
P
1

Macros are probably what you want, because they don't evaluate their arguments, so if you pass a variable name, you get a variable name, not its value.

INCF does exactly what you want, so if you google "defmacro incf" you'll find a whole bunch of definitions for this, some of which are even close to being correct. :-)

Edit: I was suggesting INCF not as an alternative to writing your own, but because it does what you want, and is a macro so you can easily find source code for it, e.g., ABCL or CMUCL.

Piper answered 11/8, 2009 at 19:0 Comment(1)
Thanks! I knew about incf by the way, this was just an example to demonstrate the problem of course, I wasn't trying to reimplement incf.Albatross
U
0

I think you are missing out on one of the key concepts of functional programming - you are not supposed to change the state of objects once they have been created. Changing something via a reference violates that.

Ulrica answered 8/8, 2009 at 21:58 Comment(3)
destruction is common in programmingGoodygoody
You shouldn't assume that one must program in a functional style in Common Lisp. One of the nice things about it is that it is multi-paradigm and doesn't force a style of programming on you. If you want to see an example of where this fact is used explicitly to frame an introductory text to the language, check out D. Touretsky's "Common Lisp: A Gentle Introduction to Symbolic Computation".Millsap
It is nonetheless well-supported in Common Lisp, which is not a pure functional language.Dispassion
H
0

You can produce similar effect by using return values, this is a possible design pattern in Lisp, even though it is not passing a variable by reference.

Like this:

(defun increase (subject increment)
  (+ subject increment))

(let ((my-var 0))
  (message "Initial value: %s" my-var)
  (setq my-var (increase my-var 25))
  (message "Final value: %s" my-var))
Hatter answered 11/2, 2020 at 6:6 Comment(0)
D
-3

As a beginner I came here to figure out how to do what should be a trivial procedure in any language. Most of the solutions posted above did not work properly, may have been more complicated than what was needed, or different implementations. Here is a simple solution for SBCL:

(defmacro inc-by-num (var num)
           (set var (+ (eval var) num)))

Apparently you cannot use setf b/c it restricts the scope whereas set doesn't. Also you may need to use eval before var if you get an "argument X is not a number" error.

Deutoplasm answered 27/12, 2011 at 15:0 Comment(2)
Greg's solution is much better, you don't run into weird risks calling eval all the time...Hemimorphic
No. This does not work like you might think it does. I don't have the time and place here to show the intricate mechanisms that happen to make you believe that the effect you desire is achieved, so I'll just drop a hint: the setting here takes place at macro expansion time.Nikianikita

© 2022 - 2024 — McMap. All rights reserved.