From: Lyman S. Taylor
Subject: ASSOC and "Should be prepared to signal..."
Date: 
Message-ID: <6ibms5$goh@pravda.cc.gatech.edu>
I know how to "fix" this. I'm just curious why this "problem" 
exists in the first place.....  

Being in a situation where I deal with written in numerous folks (a new
batch every school Quarter even...) writing code 
using a wide variety of Common Lisp environments... I'm somewhat 
confused as to why certain implementations (there are more than one)
consider: 

     ( ( a 0 )  b   ( c 2 ) ) 

a legal association list.   Is this some sort of legacy "feature" 
someone dreamed up and is a defacto standard?   It certainly 
seems in conflict with CLtL2 and the HyperSpec.  Namely, that an
association list is:

From the HyperSpec

 association list   n. a list of conese representing .....


Also in the HyperSpec is the following tidbit in ASSOC's entry...

  Exceptional Situations:

  Should be prepared to signal an error of type type-error if alist is
  not an association list.

Now "Should be prepared" has a special meaning. From my reading that
means that the "property" in question is check in a "lazy"
fashion.  If not run run across in the normal of course of performing
the operation... then "no harm, no foul".  The specific example is

    (find 'a '(a b . c ))  ==> a 

    (find 'd '(a b . c ))  ==> in safe mode must invoke error, otherwise 
                                 implementation dependent response.

This all seems to motviated for performance reasons. It would take 
"extra" effort to check to see if the arguments were of the correct
type... so don't.  

OK, but.... [ actually the same result if safety is not in "safe mode"
but that's suppose to be implementation specific..]

  (defun assoc-demo () 
    (declare (optimize safety) )
    (assoc 'd '( (a 0) b ( c 2) )))
                   

(assoc-demo ) ===> NIL  ;; huh???

Seemingly this should result in an error being signalled in all standard
compliant environments.  

What's confusing is that ASSOC would have to *extra* measures NOT signal
an error in this case. Stripping away the optional args and what-not....

  (defun my-assoc ( key a-list )
    (unless (endp a-list)
	(let ( ( association (first a-list)) )
	  (if  (eql key (car association ))
	       association
	       (my-assoc key (rest a-list))))))


will "die" if not passed an a-list.  Maybe not producing an error message
specific to assoc but.... it will "die". 

In order not to die you'd have to do something like..

  (defun my-assoc ( key a-list )
    (unless (endp a-list)
	(let ( ( association (first a-list)) )
	  (cond ((atom association) (my-assoc key (rest a-list))) ;;***
	        ((eql key (car association ))   association)
	        (t (my-assoc key (rest a-list)))))))

where if the "***" line signaled an error rather than simply recursed...
you'd have the behaviour outlined in standard. So where is the 
"extra" effort required? 

Which leads me to believe that this was done on purpose. Why? 


-- 
					
Lyman S. Taylor            "There is something unexplainable here, Scully,
(·····@cc.gatech.edu)           but it is certainly not unidentifiable."
						Fox Mulder - X-Files

From: Barry Margolin
Subject: Re: ASSOC and "Should be prepared to signal..."
Date: 
Message-ID: <Pvn21.3$5r.277013@cam-news-reader1.bbnplanet.com>
In article <··········@pravda.cc.gatech.edu>,
Lyman S. Taylor <·····@cc.gatech.edu> wrote:
>What's confusing is that ASSOC would have to *extra* measures NOT signal
>an error in this case. Stripping away the optional args and what-not....
>
>  (defun my-assoc ( key a-list )
>    (unless (endp a-list)
>	(let ( ( association (first a-list)) )
>	  (if  (eql key (car association ))
>	       association
>	       (my-assoc key (rest a-list))))))
>
>
>will "die" if not passed an a-list.  Maybe not producing an error message
>specific to assoc but.... it will "die". 
>
>In order not to die you'd have to do something like..
>
>  (defun my-assoc ( key a-list )
>    (unless (endp a-list)
>	(let ( ( association (first a-list)) )
>	  (cond ((atom association) (my-assoc key (rest a-list))) ;;***
>	        ((eql key (car association ))   association)
>	        (t (my-assoc key (rest a-list)))))))
>
>where if the "***" line signaled an error rather than simply recursed...
>you'd have the behaviour outlined in standard. So where is the 
>"extra" effort required? 

I suspect it comes from the requirement to skip over NIL as an element of
an a-list.  Your first version of MY-ASSOC will produce the wrong answer
to:

(my-assoc nil '((a 1) nil (nil 3)))

Your first version will return NIL (because (car nil) => NIL), but the
second will return the correct answer, (NIL 3).

Of course, a trivial fix to your first version will make it correct and
still fail properly on a bad a-list (assuming CAR is doing type checking):

(defun my-assoc (key a-list)
  (unless (endp a-list)
    (let ((association (first a-list)))
      (cond ((null association) (my-assoc key (rest a-list)))
            ((eql key (car association)) association)
            (t (my-assoc key (rest a-list)))))))

-- 
Barry Margolin, ······@bbnplanet.com
GTE Internetworking, Powered by BBN, Cambridge, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
From: Lyman S. Taylor
Subject: Re: ASSOC and "Should be prepared to signal..."
Date: 
Message-ID: <6idoh9$od8@pravda.cc.gatech.edu>
In article <·················@cam-news-reader1.bbnplanet.com>,
Barry Margolin  <······@bbnplanet.com> wrote:
>In article <··········@pravda.cc.gatech.edu>,
>Lyman S. Taylor <·····@cc.gatech.edu> wrote:
...
>
>I suspect it comes from the requirement to skip over NIL as an element of
>an a-list. 

    Yeah but the "fix" for that is so trivial. I brushed that off
    along with the optional argument functionality. However, thinking
    about it some more, at least one of the environments in question 
    takes dotted lists ( uses ATOM where ENDP/NULL should be) in 
    numerous places where only a proper list should be. So I guess I 
    shouldn't be surprised that ATOM gets used instead of NULL. 


  If one doesn't want to depend upon car to do the right thing (in safe
  mode it should), there is  an addtional test. Although if non-conses are 
 "rare" the following manages to avoid the extra test most of the time.

(defun my-assoc (key a-list)
  (unless (endp a-list)
    (let ((association (first a-list)))
      (cond ((atom  association) 
	      (if (null association) 
                  (my-assoc key (rest a-list))
                  (check-type association cons )))
            ((eql key (car association)) association)
            (t (my-assoc key (rest a-list)))))))


  It seems as though implementations that want to do the 
  "dubious thing" in unsafe code, but still do the "correct
  thing" in safe code would require two versions and a branch
  on "safe/unsafe" (through some sort of magic) to get the right behaviour. 



-- 
					
Lyman S. Taylor            "There is something unexplainable here, Scully,
(·····@cc.gatech.edu)           but it is certainly not unidentifiable."
						Fox Mulder - X-Files