Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lisp argument pointer

I'm learning lisp and I have to return modified input arguments from my function in Lisp.

Consider this easy example:

(defun swap (l1 l2)
  (let ((temp))
    (setf temp l1)
    (setf l1 l2)
    (setf l2 temp)))

(setf a (list 1 2 3))
(setf b (list 7 8 9))
(swap a b)
(print a)
(print b)

It doesn't work because I don't know how to pass reference to variable to function. Is that even possible in lisp? How can this function be solved?


UPDATE

;;; doesn't change original
(defun foo1 (x)
  (setf x (list 0 0 0)))

;;; but this does
(defun foo4 (x)
  (setf (car x) 0)
  (setf (cdr x) (list 0 0)))

The reason why I wanted to pass a variable by reference to be able to change it is that, when I have function with 3 input arguments and that function should change all of them, I think it is more elegant to change them by reference, then return list of three variables and then overwrite with them original variables:

;;; more elegant function
(defun foo (x y z)
  ;;... some work ...

  ;; Lets PRETEND this does work
  (setf x new-x)
  (setf y new-y)
  (setf z new-z))

; after this, a,b,c will have new values
(foo a b c)

;;; less elegant function
(defun foo (x y z)
  ;; ... some work ...
  (list new-x new-y new-z))

; after this, I still will have to manually set a,b,c
(setf temp (foo a b c))
(setf a (nth 0 tmp))
(setf b (nth 1 tmp))
(setf c (nth 2 tmp))

To explain why I'm trying to accomplish this is I got Hanoi towers homework. I was thinking about using three lists as stacks and use pop and push functions on them to insert and remove the "discs". I defined (move n source target temp) function, and it is recursively calling itself with n-1 change. The problem is, when I pop or push stack in recursive function, it doesn't influence stacks outside. If I want my move function to return stacks after n movements, should I really return list of new stacks (that less elegant function) instead of editing them by reference (that more elegant function)

What is the proper way in Functional languages?

like image 911
Buksy Avatar asked Dec 03 '22 00:12

Buksy


2 Answers

First of all, if you are learning functional programming or Lisps in general, and not just Common Lisp, don't do it. Don't try to write functions that modify state - it's not the way functional programming works. If you ever need function that exchanges 2 values, just write functions that return them in reverse order.

If you are still interested in exchanging 2 values, see this similar question for several very good suggestions. Most important are macros and manual references (wrappers for actual values).

However these answers don't include one important concept, available only in Common Lisp and not most other Lisp dialects - places. But first let's recall 2 ways to pass variable to function. Consider following example in C++:

void f(int x) {
    ...
}
int a = 5;
f(a);

This is known as "pass-by-value" strategy: value of a is copied to parameter x. And since x is just a copy, if you modify it inside of f(), nothing will happen with original variable a.

However, in C++ you can also do the following:

void f(int& x) {    
    ...
}
int a = 5; 
f(a);

This strategy is called "pass-by-reference" - here you pass pointer to location in memory where a resides. Thus x and a point to the same piece of memory, and if you modify x, a will change too.

Functional languages, including Common Lisp, don't allow you to pass variables to functions by reference. So how setf works? It turns out that CL has concept of place (sometimes also referred as "location") that defines location in memory. setf (macro that is expanded to set special form) works directly with places and not values.

To summarize:

  1. Common Lisp, like most Lisps, allows only to pass variables to function only by value.
  2. Lisp has concept of places - locations in memory.
  3. setf works directly with places and can be used to mutate variables. Macros may be used to overcome limitations of functions.

Note, that some built-in functions in CL can return places, e.g. car, cdr, aref and also all object accessors. See this pages for some examples.

UPDATE

Your new question is where to modify values - inside the function by reference or outside without references. However, none of these would be correct in functional programming. Right answer here is: don't modify anything. In FP you normally have some state variable(s), but instead of modifying it in place you create modified copy and pass it further, so that original variable is not changed. Consider example of recursive function for calculating factorial:

(defun factorial-ex (x accum)
   (if (<= x 1) 
      accum
      (factorial-ex (- x 1) (* x accum))))

(defun factorial (x)
   (factorial-ex x 1))

factorial-ex is auxiliary function that takes one more parameter - accumulator to hold current state of calculation. On each recursion call we decrease x by 1 and multiply accum by current value of x. However, we don't change values of x and accum - we pass new values into recursive call to the function. Physically there are many copies of x and accum - one for each function call - and none of them ever changes.

(Note, that some CL implementations with particular options could use so-called tail call optimization that breaks statement about different locations in memory above, but at the moment you shouldn't worry about it.)

In your task you can do the same thing. Instead of modifying your 3 variables - either inside or outside function - make their modified copies and pass them to recursive call. In imperative programming you use variables and loops, and in functional programming you should prefer immutable values and recursion.

like image 192
ffriend Avatar answered Feb 11 '23 22:02

ffriend


The builtin macro rotateffulfills this functionality:

(setf x 1)
(setf y 3)
;x = 1, y = 3
(rotatef x y)
;x = 3, y = 1

In order to write your own function to do this, I would recommend creating a macro:

(defmacro my-swap (a b)
     `(let ((temp ,a))
          (setf ,a ,b)
          (setf ,b temp)))

However, as Clayton pointed out this macro will fail if it is applied to a variable named "temp". Hence, we can use gensym to create a new variable name (guaranteed to not be in use) and pass that to a secondary macro which actually switches the values:

(defmacro my-swap-impl (a b sym) ;;implementation of my-swap
          `(let ((,sym ,b)) ;evaluate the symbol and use it as a variable name
             (setf ,b ,a)
             (setf ,a ,sym)))

This is a version of the previous swap macro that accepts a third argument to serve as the temporary variable name. This is called from a simple macro:

(defmacro my-swap (a b) ;;simply passes a variable name for use in my-swap-impl
          `(my-swap-impl ,a ,b ,(gensym)))

This setup can be used exactly like the previous one except that it is safe from variable capture.

like image 43
ApproachingDarknessFish Avatar answered Feb 11 '23 21:02

ApproachingDarknessFish