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.
You cannot modify the contents of a variable with a function that only takes the variable's value.
Take the following simple example:
What happens?
Foo
is evaluated to the list it points to.2
evaluates to 2.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.Push
modifies the variablelist
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.Futile-push
returns the return value of thepush
form, which happens to be the new value oflist
.That return value is never used or bound, so it vanishes.
The most straightforward way to do what you want is to return the new value and then set the variable outside:
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 thatpush
is itself a macro for exactly these reasons.When you initialize
data
variable using(push (pop data) result)
, it moves items fromdata
toresult
instead of copying:What you might want to use instead is
(copy-list list)
function:When you call push in queuer, this changes the value of the binding "result", not the cons cell that result is pointing to.
is essentially equivalent to:
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:
You would instead write something like:
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:
use:
Note also that
aggregate
does not need theprogn
.Slightly advanced (don't do that):
If you have a global list:
You can't push onto it, as we have seen, like this:
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, ...):
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.But, don't do it. Program in a functional style.