This is a read-only archive of lispforum.com. The forum was locked to new users and posts and is preserved here as static HTML from a database snapshot taken on 2019-09-07.

Beginners Question - Can't manipulate a local variable

13 posts · 3146 views

Hi all,

I'm having a problem trying to get a local-variable in my Lisp code to be manipulated. I have no idea why it refuses to change!
Here is the code:
(setf list ((title "New Super Mario Bros. Wii")
	(genre "Platform")
	(platform "Wii")))

(defun rate (&optional title genre platform)
	(dolist (element list) ; element is the specific game. In this case, New Super Mario Bros. Wii.
		(setf element (append element '((rating 0))))
		(let ((game-rating 0))
			(dolist (e element) ; e will be each attibute in the element, such as (genre "Platform").
				(cond ((equal (second e) title) (+ game-rating 1))
					((equal (second e) genre) (+ game-rating 1))
					((equal (second e) platform) (+ game-rating 1))))
			(setf (second (assoc 'rating elem)) game-rating)
			(print element))))
game-rating does not seem to increment for some reason.

Any help will be appreciated.

Re: Beginners Question - Can't manipulate a local variable

webguy08 wrote:
(setf list ((title "New Super Mario Bros. Wii")
   (genre "Platform")
   (platform "Wii")))
This doesn't even work, since there is an unquoted non-valid form there, but even assuming that there it was quoted this is invalid behaviour, since you SETF is used to set places, not create variables. Most implementations will create a global variable, but this is not standard. Use DEFPARAMETER to create global variables. Also, it is an usual convention to surround the name of global variables with 'earmuffs', that is, stars, '*list*', to make it obvious that their binding is dynamic.

In any case, one of your problems here is that you can, in fact, manipulate a local variable, and you do. And then, since it is local, in immediately goes out of scope and you lose results of your manipulation.

Like in here:
(setf element (append element '((rating 0))))
you just change the local iteration variable, without touching anything else.

Also, what do you expect:
(dolist (e element) ; e will be each attibute in the element, such as (genre "Platform").
        (cond ((equal (second e) title) (+ game-rating 1))
              ((equal (second e) genre) (+ game-rating 1))
              ((equal (second e) platform) (+ game-rating 1))))
to do? Function '+' computes and returns a sum of two numbers, it doesn't change anything. Not to mention that it doesn't really make sense for other reasons, what if the title is the same as the platform?

In general, it is best to treat lists in Common Lisp as functional datastructures, since while they can be mutated, it doesn't always do what you expect, as there are no actual lists in Common Lisp as a complete ADT. If you want to use mutable records, I would suggest using classes. Or at the very least a hash table.

Re: Beginners Question - Can't manipulate a local variable

Aaahh! I feel so stupid :lol:. I didn't ever change game-rating, I just returned its value.
Sorry about the missing quote mark. I shortened the list do save space and must have accidentally deleted it.

Thanks for the help!

Re: Beginners Question - Can't manipulate a local variable

I don't want to start a new thread for the following question because it's relatively small.

Using the following list:
(setf game-list '(((title "New Super Mario Bros. Wii") (genre "Platform") (platform "Wii") (price 20))
                        ((title "Quake") (genre "First Person Shooter") (platform "PC") (price 5)))
If I wanted to sort that list according to price, how on Earth would I go about doing so?
I have tried looking into it, and have seen examples like so:
(setf l (1 4 5 6 3))
(sort l #'>)
Or something like that, but that's is a fairly simple list to sort. Here is what I have tried, but it doesn't work:
(sort game-list #'(second (fourth (first game-list))))
Any ideas?

Re: Beginners Question - Can't manipulate a local variable

See the documentation of SORT, in particular the key argument. There are even examples similar to what you want, although you probably would need a more complex anonymous function. It would be easier if the data was represented as classes, as the key would just be an accessor, rather than a partially applied assoc call.

Also note that SORT is destructive, in the sense that it destroys the sorted list, reusing its storage, and returning the sorted list. Any references to the old list will point most likely to some fragment. This also mean that you should not sort a literal list (created by QUOTE), as it is in principle read-only and the consequences are undefined and usually bad.

What are you learning Common Lisp from? This is, as are many other things, well explained in Practical Common Lisp, which is the text I would recommend. There are quite many Common Lisp tutorials on the web which are either archaic or just bad.

Re: Beginners Question - Can't manipulate a local variable

I'm just using various sources on the internet. Thanks for those 2 you have given me! I've bookmarked them, they seem to provide a lot of detail about everything.
I'll have a look into sorting; perhaps just write my own sorting method. I'm thinking of putting everything in an array and then sorting it in there since I find arrays easy to work with, and then regurgitating it.

Just a final question about Lisp :P. If you want to check several arguments do you have to use lots of 'if' statements, or is there a tidier alternative? I have used cond on several occasions, but cond doesn't execute all of the conditions, only the first one which returns true. I need something that checks each argument, and if it's true then execute it.

Thanks for the help.

Re: Beginners Question - Can't manipulate a local variable

webguy08 wrote:I'll have a look into sorting; perhaps just write my own sorting method. I'm thinking of putting everything in an array and then sorting it in there since I find arrays easy to work with, and then regurgitating it.
The standard sort function is perfectly fine (and it does work on vectors in place) as long as you remember those things I mentioned, and it is unlikely that you would be able to write anything better. For one thing, since it is a standard function the compiler is allowed to optimize it using type inference information.
webguy08 wrote:Just a final question about Lisp :P. If you want to check several arguments do you have to use lots of 'if' statements, or is there a tidier alternative? I have used cond on several occasions, but cond doesn't execute all of the conditions, only the first one which returns true.
That rather depends on what exactly you want to do. Usually if there is some code pattern then it can be extracted into a function or a macro. Remember that symbols and functions in particular are first class objects, and can be put in lists and so on.

Also note that there are WHEN and UNLESS macros for when there is no second IF branch.
webguy08 wrote:I need something that checks each argument, and if it's true then execute it.
There is any number of ways to achieve that, depending on what exactly you want. This usually indicates that your arguments should be a number of arguments, but, a possible '&rest', single parameter, to be iterated upon.

Re: Beginners Question - Can't manipulate a local variable

I said that my previous post was the last :lol:, but I seem to have come across something new which I can't explain.

Taking the list that I provided in the previous post:
(setf game-list '(((title "New Super Mario Bros. Wii") (genre "Platform") (platform "Wii") (price 20))
                      ((title "Quake") (genre "First Person Shooter") (platform "PC") (price 5)))
If I create a function like so:
(defun add-to-list () (setf game-list (append game-list (list (list (list title "Doom") (genre "First Person Shooter") (platform "PC") (price 5))))))
And I write (add-to-list) this would change the list, thus when I call game-list in Lisp it will return the list with that new game. If however I write a function like so:
(defun add-to-list (l) (setf l (append l (list (list (list title "Doom") (genre "First Person Shooter") (platform "PC") (price 5))))))
And I write (add-to-list game-list) in Lisp, it will not actually change the list. If I call it, it will not have the new game.

I don't understand why this is. Any idea?

Re: Beginners Question - Can't manipulate a local variable

I did explain this in the first reply. In the first case, you use a non-standard automatic global variable (double bad style), in the second you create a new list with appended content and assign it to a local variable, which immediately goes out of scope.

In neither case you 'change the list', since APPEND is explicitly non-destructive, and even if it were (like NCONC) it is a bad idea to use destructive functions for side effects. You just assign a new value to a variable.

Re: Beginners Question - Can't manipulate a local variable

I tried changing the list's name to *game-list* but that didn't change the results, so I take it this is just common practice? So, at the moment Lisp thinks that I'm passing in a list and it should treat it as a local variable? How would I make it understand that I want it to change the global variable? Do I have to use defvar, or defparameter? :|

After coming from object-oriented languages this sure is very different :)

Re: Beginners Question - Can't manipulate a local variable

(defun add-to-list (l) (setf l ...))
In this snippet, you're passing a *value* to l and then changing its local value. Its very much like the following C++ or Java snippets.
void f1(int x) { x=5; }
void f2(List l) { l=new List(); }
In lisp, it is generally considered poor form to change the value of lists which are passed as arguments, but it can be done. Just as with objects in any other language, you have to directly mutate the object's members (here the CAR and CDR of the first CONS cell).
;; Good: have add-to-list return the modified list; the caller can change its variable if desired
(setf l (add-to-list l))
;; Bad: have add-to-list modify the list
(defun add-to-list (l)
  (when l
    (setf (car l) 1
          (cdr l) 2))
  l)
In C++/Java, this last version might look like
List addToList(List l)
{
  if(!l.null())
  {
    l.car=1;
    l.cdr=2;
  }
  return l;
}
Why not modify lists? A decent garbage collector makes it incredibly cheap to make new lists (speed comparable to stack allocation), so there's little reason to modify the old ones. Plus the list in an argument is often a subset of a greater structure, so you have to be careful not to mess that up. Put together, there's little need and a potentially great nuisance. Some people even name functions that modify lists with an exclamation (e.g. function!), just to emphasize the danger.

Re: Beginners Question - Can't manipulate a local variable

webguy08 wrote:How would I make it understand that I want it to change the global variable? Do I have to use defvar, or defparameter?
You use DEFPARAMETER once to declare that the variable is global and then change it. But operating on global variables directly, even if they are convenient for testing, is usually bad, for reasons which are not language specific.
webguy08 wrote:After coming from object-oriented languages this sure is very different
It is not really. You are just for some reason doing things in most weird way possible, and in any case, in addition to what nuntius said, it is exactly the same in Python:
>>> gameList=['a','b','c']
>>> gameList
['a', 'b', 'c']
>>> def addToList(l):
...   l=l+['d']
...
>>> addToList(gameList)
>>> gameList
['a', 'b', 'c']
and in any language with value-passing semantics, which most 'object-oriented' languages are.

In any case, as I pointed out in the very first reply, that Lisp most certainly has objects, and you should be using them.

Re: Beginners Question - Can't manipulate a local variable

At the moment I'm just trying to get to grips with how Lisp works, before I start declaring classes and such.
It completely makes sense now after seeing the Java examples!!! In fact, I've gotten it to do what I want now :D.

Btw I really appreciate all the help guys.