Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Common lisp push from function

I have the following common lisp functions: (aggregate line1 line2) and (queuer data result).

queuer should push into result either the values line1 and line2 if they have the 1st field different, or the aggregate of those 2 lines if they have the 1st field equal.

I do not know why it doesn't change my result list.

Note: I am initializing the result list with a (push (pop data) result) to have the first element there. The 2 lists are 1-depth nested lists (("1" "text") ("2" "text") (...)).

(defun aggregate (line1 line2)
  (progn
    (list 
     (nth 0 line1)
     (nth 1 line1)
     (nth 2 line1)
     (concatenate 'string (nth 3 line1) ", " (nth 3 line2))
     (concatenate 'string (nth 4 line1) ", " (nth 4 line2)))))

(push (pop x) y)

(defun queuer (data result)
  (loop do
       (let ((line1 (pop data))
             (line2 (pop result)))
         (if (equal (first line1) (first line2))
             (progn
               (push (aggregate line1 line2) result)
               (print "=="))
             (progn
               (push line2 result)
               (push line1 result)
               (print "<>"))))
       while data))

Thank you for any insights.

like image 435
emanuel.manea Avatar asked May 06 '13 09:05

emanuel.manea


People also ask

How do you define a function in a Common Lisp?

Use defun to define your own functions in LISP. Defun requires you to provide three things. The first is the name of the function, the second is a list of parameters for the function, and the third is the body of the function -- i.e. LISP instructions that tell the interpreter what to do when the function is called.

Which function is used to return the type of any LISP Object?

The typep predicate is used for finding whether an object belongs to a specific type. The type-of function returns the data type of a given object.

Does Common Lisp have types?

Common Lisp has a complete and flexible type system and corresponding tools to inspect, check and manipulate types. It allows creating custom types, adding type declarations to variables and functions and thus to get compile-time warnings and errors.

What does cons do in LISP?

In computer programming, cons (/ˈkɒnz/ or /ˈkɒns/) is a fundamental function in most dialects of the Lisp programming language. cons constructs memory objects which hold two values or pointers to two values. These objects are referred to as (cons) cells, conses, non-atomic s-expressions ("NATSes"), or (cons) pairs.


3 Answers

If you write functions in Lisp it is preferable to think 'functionally'. A function takes values and returns values. A typical rule would be to avoid side effects. So your function should return a result value, not 'modify' a variable value.

Instead of:

(defparameter *result* '())

(defun foo (a)
   (push a *result*))

use:

(defparameter *result* '())

(defun foo (a result)
  (push a result)
  result)

(setf *result* (foo a *result*))

Note also that aggregate does not need the progn.

Slightly advanced (don't do that):

If you have a global list:

(defparameter *foo* '())

You can't push onto it, as we have seen, like this:

(defun foo (l)
   (push 1 l))

If you call foo the variable *foo* is unchanged. Reason: Lisp does not pass a variable reference, it passes the value of the variable.

But how can we pass a reference? Well, pass a reference: a cons cell would do it (or a structure, a vector, a CLOS object, ...):

CL-USER 38 > (defparameter *foo* (list '()))
*FOO*

CL-USER 39 > (defun foo (ref)
               (push 1 (first ref)))
FOO

CL-USER 40 > (foo *foo*)
(1)

CL-USER 41 > (foo *foo*)
(1 1)

Now, if we look at *foo*, it is changed. But we haven't really changed the variable. We have changed the first entry of the list.

CL-USER 42 > *foo*
((1 1))

But, don't do it. Program in a functional style.

like image 173
Rainer Joswig Avatar answered Oct 22 '22 06:10

Rainer Joswig


You cannot modify the contents of a variable with a function that only takes the variable's value.

Take the following simple example:

(defun futile-push (thing list)
  (push thing list))

(let ((foo (list 1)))
  (futile-push 2 foo))

What happens?

  • Foo is evaluated to the list it points to.
  • 2 evaluates to 2.
  • These two arguments are passed to the function.

Inside the function invocation:

  • Thing is now bound to 2.
  • List is now bound to the list (1).

Note that the list does not know that it is also referenced by the variable foo outside the function.

         foo
          |
          v
        ---------
list -> | 1 |NIL|
        ---------
  • Push modifies the variable list in such a way that it is now bound to the list (2 1).

Note that this does not affect foo outside. Foo still points to the same thing as before.

                     foo
                      |
                      v
        ---------   ---------
list -> | 2 | ----> | 1 |NIL|
        ---------   ---------
  • Futile-push returns the return value of the push form, which happens to be the new value of list.

  • That return value is never used or bound, so it vanishes.

     foo
      |
      v
    ---------
    | 1 |NIL|
    ---------
    

The most straightforward way to do what you want is to return the new value and then set the variable outside:

(let ((foo (list 1)))
  (setf foo (not-so-futile-push 2 foo)))

If you need to do that at more than one place, it might be worthwhile to write a macro for that which expands to the setf form. Note that push is itself a macro for exactly these reasons.

like image 23
Svante Avatar answered Oct 22 '22 06:10

Svante


When you call push in queuer, this changes the value of the binding "result", not the cons cell that result is pointing to.

(push x list)

is essentially equivalent to:

(setq list (cons x list))

As long as your queuer function is a function, it couldn't really be any other way. If you call it with the argument "my-queue", then that argument (a symbol) is evaluated when you call the function and the result of the evaluation -- a cons cell -- is passed to the function. There is no way to modify that cons cell to indicate that another cons cell should be "prepended" to it -- cons cells don't keep track of the things that point to them.

There are (at least) three possible solutions:

  • Write your code so that queuer returns the new queue, instead of expecting the argument to be modified (or "mutated").

  • Wrap the queue inside a mutable layer of indirection. You could for instance hold the queue in the car or the cdr of a cons cell. You would then be able to mutate (car result) or (cdr result) in your queuer function, for instance with push.

  • Convert queuer to be a macro instead of a function. You can then write code to mutate its argument that will essentially be 'inserted' in your code wherever you use the queuer macro.

I would personally recommend the first solution. Where you would then, if you had your mutating queuer, want to write:

(queuer-mutating data my-queue)

You would instead write something like:

(setf my-queue (queuer-not-mutating data my-queue))
like image 23
svk Avatar answered Oct 22 '22 06:10

svk