From: charlieb
Subject: Mutating a subset of a global within a function
Date: 
Message-ID: <2ravsiF1822g1U1@uni-berlin.de>
Hi,
	I have an issue with the code below. It's a really simple database with 
a list of records each of which is an a-list of fields. I want add-field 
to mutate *records*, find-max having returned a single record from 
*records* but as you can see it doesn't work. I think I understand why; 
*records* is a special and gets rebound for each frame but I can't 
figure out a way around it.
	
Thanks,
Charlieb.

Here's a little session demonstrating the problem:

CL-USER> *records*
(((GLOBAL-UNIQUE-ID . 0)))
CL-USER> (add-field (make-field 'test 'one) (find-max global-unique-id))
((TEST . ONE) (GLOBAL-UNIQUE-ID . 0))
CL-USER> *records*
(((GLOBAL-UNIQUE-ID . 0)))
CL-USER>

; ----- CODE BEGINS -----
(defparameter *records* (list (list (cons 'global-unique-id 0)))
   "Holds all the records as a list of lists")


; All selectors that use *records* will return full records
;
; All selectors that take a record will return the full record:
;      a cons of the record and it's name
;

; -------------- FIELD LEVEL OPERATORS
(defun value (field)
   (cdr field))
(defun name (field)
   (car field))
(defun field (name record)
   (assoc name record))
(defun make-field (name value)
   (cons name value))

; -------------- FIELD/RECORD OPERATORS

(defun add-field (field record)
   (push field record))

; -------------- RECORD LEVEL OPERATORS

(defun filter (field &optional)
   (remove-if-not (lambda (record)
		   (eql field (field (car field) record)))
		 records))

(defun make-record (&optional fields)
   (cons (make-field 'global-unique-id
		    (1+ (value (field 'global-unique-id
				      (find-max 'global-unique-id)))))
	fields))

; --------------- RECORD/DATABASE OPERATORS

(defun add-record (record)
   (push record *records*))

; --------------- DATABASE LEVEL OPERATORS

(defun find-max (field &key (pred #'>))
   "Returns the whole record!"
   (let ((max (value (field field (car *records*))))
	(result (car *records*)))
     (dolist (record (cdr *records*) result)
       (when (funcall pred (value (field field record)) max)
	(setq max (value (field field record))
	      result record)))))

From: Peter Seibel
Subject: Re: Mutating a subset of a global within a function
Date: 
Message-ID: <m3acvjcwlo.fsf@javamonkey.com>
charlieb <··@privacy.net> writes:

> Hi,
> 	I have an issue with the code below. It's a really simple
> database with a list of records each of which is an a-list of fields.
> I want add-field to mutate *records*, find-max having returned a
> single record from *records* but as you can see it doesn't work. I
> think I understand why; *records* is a special and gets rebound for
> each frame but I can't figure out a way around it.

No, the problem is that the PUSH in add-field is just modifying the
local variable, record. You need to pass PUSH the "place" you want to
modify. One way to do that is to pass it, not the record you want to
modify but the sublist of *records* that starts with the record you
want to modify. Then you can write add-field like this:

  (defun add-field (field records)
    (push field (car records)))

For this to work, you'll need to change find-max to something like
this (untested):

  (defun find-max (field &key (pred #'>))
    "Returns the whole record!"
    (loop for cons on *records*
          for value = (value (field field (car cons)))
          do (when (funcall pred value max)
               (setq max value result cons))))

The point is since you want to modify *records* you need to pass
add-field a cons cell that is part of the list structure of *records*.

-Peter

-- 
Peter Seibel                                      ·····@javamonkey.com

         Lisp is the red pill. -- John Fraser, comp.lang.lisp
From: charlieb
Subject: Re: Mutating a subset of a global within a function
Date: 
Message-ID: <2rcpn8F1911mdU1@uni-berlin.de>
That makes sense. Let's see if I can summarise with cons cell diagrams.

*records* =>
[ V |   ]
   [ V |   ]
     [GUID| 0 ]

(find-max ...) =>
[ V |   ]
   [GUID| 0 ]

(add-field ...) =>
[ FIELD | V  ]
	  [ V |   ]  -- But *records* is still pointing here.
             [GUID| 0 ]

Whereas with your version:

(find-max ...) => *records* in this case =>
[ V |   ]
   [ V |   ]
     [GUID| 0 ]

I'm not entirely sure about what the results of push car would be in 
this case. Something like:
(add-field ...) =>
[ V |   ]
   [ V               |  V ]
     [ FIELD | VAL ]    [ V |   ]
                          [GUID| 0 ]

Thanks for clearing that up for me.
Charlieb.
From: Harald Hanche-Olsen
Subject: Re: Mutating a subset of a global within a function
Date: 
Message-ID: <pco1xgv780e.fsf@shuttle.math.ntnu.no>
+ charlieb <··@privacy.net>:

| Hi,
| 	I have an issue with the code below. It's a really simple
| 	database with a list of records each of which is an a-list of
| 	fields. I want add-field to mutate *records*, find-max having
| 	returned a single record from *records* but as you can see it
| 	doesn't work. I think I understand why; *records* is a special
| 	and gets rebound for each frame but I can't figure out a way
| 	around it.

Yes, *records* is special, but its binding remains unchanged.  Your
problems is elsewhere.

| (defun add-field (field record)
|    (push field record))

This is useless.  The push here only changes the value of the local
variable record, which ceases to exist the moment the function
returns.

| (defun find-max (field &key (pred #'>))
|    "Returns the whole record!"
|    (let ((max (value (field field (car *records*))))
| 	(result (car *records*)))
|      (dolist (record (cdr *records*) result)
|        (when (funcall pred (value (field field record)) max)
| 	(setq max (value (field field record))
| 	      result record)))))

This is ok as far as it goes, except that you really want to mutate
the /place/ where the result is found, not the result itself, by
pushing something on the front of the list.

If you were to introduce the following changes, then your example
might just work:

(defun add-field (field records)
   (push field (car records)))

(defun find-max-place (field &key (pred #'>))
  "Returns the whole record!"
  (loop with max = (value (field field (car *records*)))
	and result = *records*
	for records on (cdr *records*)
	for val = (value (field field (car records)))
	when (funcall pred val max)
	do (setf max val
		 result records)
	finally (return result)))

TEST> (add-field (make-field 'test 'one) (find-max-place 'global-unique-id))
((TEST . ONE) (GLOBAL-UNIQUE-ID . 0))
TEST> *records*
(((TEST . ONE) (GLOBAL-UNIQUE-ID . 0)))

I suspect this is far from the best interface though, as the use of
add-field and find-max-place as given here seems a bit
counterintuitive.  But I'll leave you to work out a better interface.
I hope at least my explanation of what went wrong makes sense.

-- 
* Harald Hanche-Olsen     <URL:http://www.math.ntnu.no/~hanche/>
- Debating gives most of us much more psychological satisfaction
  than thinking does: but it deprives us of whatever chance there is
  of getting closer to the truth.  -- C.P. Snow