From: Matt
Subject: Enumerations
Date: 
Message-ID: <3aKEf.5172$Nv2.4736@newsread1.news.atl.earthlink.net>
How are enumerations typically done in Lisp?  Do you use
keywords or symbols or what?  Is there a way to restrict
which symbols can be assigned to an enumeration variable
or would I have to write a runtime function for that?

Thanks!
Matt

From: Raffael Cavallaro
Subject: Re: Enumerations
Date: 
Message-ID: <2006020310061116807-raffaelcavallaro@pasdespamsilvousplaitmaccom>
On 2006-02-03 09:55:59 -0500, Matt <·····@invalid.net> said:

> How are enumerations typically done in Lisp?  Do you use
> keywords or symbols or what?  Is there a way to restrict
> which symbols can be assigned to an enumeration variable
> or would I have to write a runtime function for that?
> 
> Thanks!
> Matt

If you're talking about generalized enumerations:

<http://common-lisp.net/project/cl-enumeration/>

If you just want c-style enums it might be a bit of overkill.
From: Matt
Subject: Re: Enumerations
Date: 
Message-ID: <eIKEf.11183$vU2.9058@newsread3.news.atl.earthlink.net>
Raffael Cavallaro wrote:
> On 2006-02-03 09:55:59 -0500, Matt <·····@invalid.net> said:
> 
>> How are enumerations typically done in Lisp?  Do you use
>> keywords or symbols or what?  Is there a way to restrict
>> which symbols can be assigned to an enumeration variable
>> or would I have to write a runtime function for that?
>>
>> Thanks!
>> Matt
> 
> 
> If you're talking about generalized enumerations:
> 
> <http://common-lisp.net/project/cl-enumeration/>
> 
> If you just want c-style enums it might be a bit of overkill.

Yeah, C-style enums.  I was just wondering how they're typically
done or how they're typically avoided (or however you want to say
it) in Lisp.

Thanks!
Matt
From: Pascal Costanza
Subject: Re: Enumerations
Date: 
Message-ID: <44hb9oF2518dU1@individual.net>
Matt wrote:
> How are enumerations typically done in Lisp?  Do you use
> keywords or symbols or what?  Is there a way to restrict
> which symbols can be assigned to an enumeration variable
> or would I have to write a runtime function for that?

I think that keywords or symbols are quite appropriate here. If you need 
to typecheck them, you can construct a member type.

(typep 'b '(member a b c))


Pascal

-- 
My website: http://p-cos.net
Closer to MOP & ContextL:
http://common-lisp.net/project/closer/
From: Matt
Subject: Re: Enumerations
Date: 
Message-ID: <vPKEf.11187$vU2.8702@newsread3.news.atl.earthlink.net>
Pascal Costanza wrote:
> Matt wrote:
> 
>> How are enumerations typically done in Lisp?  Do you use
>> keywords or symbols or what?  Is there a way to restrict
>> which symbols can be assigned to an enumeration variable
>> or would I have to write a runtime function for that?
> 
> 
> I think that keywords or symbols are quite appropriate here. If you need 
> to typecheck them, you can construct a member type.
> 
> (typep 'b '(member a b c))

Oh very cool.  Thank you!  I was just playing with the repl
and I guess keywords are constants, so that seems like a good
thing to take into consideration when you're making an enumeration.
Or maybe you should always use symbols in case you want to go
from valueless enumeration constants to valued ones later on, if
your requirements change or something.

Matt
From: Rob Warnock
Subject: Re: Enumerations
Date: 
Message-ID: <McadnQ9obK_Ru3neRVn-tA@speakeasy.net>
Matt  <·····@invalid.net> wrote:
+---------------
| How are enumerations typically done in Lisp?  Do you use
| keywords or symbols or what?
+---------------

Besides symbols (which includes keywords), as others have already
commented upon, sometimes people *do* use something close to C-style
enumerations, especially when the values have to be encoded into bits
of data or written to external files (or translated to C!). One example
can be found in CMUCL, in "src/compiler/generic/vm-macs.lisp":

    (defmacro defenum ((&key (prefix "") (suffix "") (start 0) (step 1))
		       &rest identifiers)
      (let ((results nil)
	    (index 0)
	    (start (eval start))
	    (step (eval step)))
	(dolist (id identifiers)
	  (when id
	    (multiple-value-bind
		(root docs)
		(if (consp id)
		    (values (car id) (cdr id))
		    (values id nil))
	      (push `(defconstant ,(intern (concatenate 'simple-string
							(string prefix)
							(string root)
							(string suffix)))
		       ,(+ start (* step index))
		       ,@docs)
		    results)))
	  (incf index))
	`(eval-when (compile load eval)
	   ,@(nreverse results))))

This is used in other parts of the compiler for such things as
assigning values to lowtag bits in objects or heap object header
types, e.g., from "src/compiler/generic/objdef.lisp":

    (in-package "VM")
    ...

    ;;; The main types.  These types are represented by the
    ;;; low three bits of the pointer or immeditate object.
    ;;; 
    (defenum (:suffix -type)
      even-fixnum
      function-pointer
      other-immediate-0
      list-pointer
      odd-fixnum
      instance-pointer
      other-immediate-1
      other-pointer)

    ;;; The heap types. Each of these types is in the header
    ;;; of objects in the heap.
    ;;; 
    (defenum (:suffix -type
	      :start (+ (ash 1 lowtag-bits) other-immediate-0-type)
	      :step (ash 1 (1- lowtag-bits)))
      bignum
      ratio
      single-float
      double-float
      complex
      complex-single-float
      complex-double-float
      
      simple-array
      simple-string
      simple-bit-vector
      ...		; [and many, many more!!]
      )

This results in DEFCONSTANTs of each name (concatenated with
the :SUFFIX value) to a distinct numeric value, e.g.:

    > (describe 'vm:function-pointer-type)

    FUNCTION-POINTER-TYPE is an external symbol in the X86 package.
    It is a constant; its value is 1.
    It is defined in:
    target:compiler/generic/objdef.lisp
    > (list vm:bignum-type
	    vm:ratio-type
	    vm:single-float-type
	    vm:double-float-type
	    vm:complex-type
	    vm:complex-single-float-type
	    vm:complex-double-float-type)
    (10 14 18 22 26 30 34)
    > 


-Rob

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Matt
Subject: Re: Enumerations
Date: 
Message-ID: <lp3Ff.12274$vU2.8675@newsread3.news.atl.earthlink.net>
Rob Warnock wrote:

>     (defmacro defenum ((&key (prefix "") (suffix "") (start 0) (step 1))
> 		       &rest identifiers)
>       (let ((results nil)
> 	    (index 0)
> 	    (start (eval start))
> 	    (step (eval step)))
> 	(dolist (id identifiers)
> 	  (when id
> 	    (multiple-value-bind
> 		(root docs)
> 		(if (consp id)
> 		    (values (car id) (cdr id))
> 		    (values id nil))
> 	      (push `(defconstant ,(intern (concatenate 'simple-string
> 							(string prefix)
> 							(string root)
> 							(string suffix)))
> 		       ,(+ start (* step index))
> 		       ,@docs)
> 		    results)))
> 	  (incf index))
> 	`(eval-when (compile load eval)
> 	   ,@(nreverse results))))

Argh!  That code gave me a headache :-), but yeah, I could see why
someone would want to do that:

>     (defenum (:suffix -type)
>       even-fixnum
>       function-pointer
>       other-immediate-0
>       list-pointer
>       odd-fixnum
>       instance-pointer
>       other-immediate-1
>       other-pointer)

Matt
From: Frank Buss
Subject: Re: Enumerations
Date: 
Message-ID: <4jjoljn7zb8j.j9s3uutagbvx$.dlg@40tude.net>
Rob Warnock wrote:

>     (defmacro defenum ((&key (prefix "") (suffix "") (start 0) (step 1))
> 		       &rest identifiers)
>       (let ((results nil)
> 	    (index 0)
> 	    (start (eval start))
> 	    (step (eval step)))
> 	(dolist (id identifiers)
> 	  (when id
> 	    (multiple-value-bind
> 		(root docs)
> 		(if (consp id)
> 		    (values (car id) (cdr id))
> 		    (values id nil))
> 	      (push `(defconstant ,(intern (concatenate 'simple-string
> 							(string prefix)
> 							(string root)
> 							(string suffix)))
> 		       ,(+ start (* step index))
> 		       ,@docs)
> 		    results)))
> 	  (incf index))
> 	`(eval-when (compile load eval)
> 	   ,@(nreverse results))))

I've done something similar for converting anonymous C enums to Lisp:

http://article.gmane.org/gmane.lisp.cffi.devel/478/match=defenum

(defmacro defenum (&body enums)
  `(progn ,@(loop for value in enums
                  for index = 0 then (1+ index)
                  when (listp value) do (setf index (second value)
                                              value (first value))
                  collect `(defconstant ,value ,index))))

And while not as powerful with prefix and suffix, I think my macro looks
nicer :-)

-- 
Frank Buss, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Rob Warnock
Subject: Re: Enumerations
Date: 
Message-ID: <L8udnY10qcSD73jenZ2dnUVZ_t-dnZ2d@speakeasy.net>
Frank Buss  <··@frank-buss.de> wrote:
+---------------
| Rob Warnock wrote [quoting from CMUCL sources]:
| >     (defmacro defenum ((&key (prefix "") (suffix "") (start 0) (step 1))
| > 		       &rest identifiers)
| >       ...)
| 
| I've done something similar for converting anonymous C enums to Lisp:
| http://article.gmane.org/gmane.lisp.cffi.devel/478/match=defenum
|   (defmacro defenum (&body enums)
|     `(progn ,@(loop for value in enums
|                     for index = 0 then (1+ index)
|                     when (listp value) do (setf index (second value)
|                                                 value (first value))
|                     collect `(defconstant ,value ,index))))
| And while not as powerful with prefix and suffix, I think my macro
| looks nicer :-)
+---------------

Indeed! I suspect the main reason the CMUCL version has all that prefix
and suffix stuff is because of a grotesquely horrible hack elsewhere in
the compiler which uses introspection of certain packages to extract values
from symbols with magic prefixes/suffixes to create certain source files
when recompiling itself!! Specifically, look at the functions GENESIS,
EMIT-C-HEADER, & EMIT-C-HEADER-AUX in the file "src/compiler/generic/
new-genesis.lisp". [But have a barf bag handy...]  ;-}


-Rob

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Thomas A. Russ
Subject: Re: Enumerations
Date: 
Message-ID: <ymiwtg8mjn8.fsf@sevak.isi.edu>
I would just use keywords.

Normally I wouldn't worry about symbol assignments to variables.  Those
are the sorts of things that would typically be handled by the ECASE
statement that is used to process the enumeration values.  It doesn't
really matter that much to me exactly where my run-time errors come up.

I did, once, write some code which encoded enumeration-like keywords as
fixnum-encoded bit vectors when I was trying to get very compact code.
Except for a dearth of documentation, the code works quite well.  I
suppose I should clean it up and submit it to a repository somewhere....


-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Alan Crowe
Subject: Re: Enumerations
Date: 
Message-ID: <86vevr3xyj.fsf@cawtech.freeserve.co.uk>
Matt <·····@invalid.net> writes:
> How are enumerations typically done in Lisp?  Do you use
> keywords or symbols or what?  Is there a way to restrict
> which symbols can be assigned to an enumeration variable
> or would I have to write a runtime function for that?

I think that the usual issue that one bumps into is that
CL:CASE doesn't evaluate its keys. So if you do

(defconstant load 0)
(defconstant store 3)
(defconstant add 4)

you try to do

(case operation
  (load (fetch ...))
  (store (write ...))
  ...)

and it doesn't work. So what do you do instead?

(case operation
  (#.load (fetch ...

is ugly

(case operation
  (0 (fetch ...

is just giving up.

One approach is to go back to first principles and ask why
is there an issue at all? What is wrong with just using
symbols?

I think the answer is that using symbols is the Lisp way and
works very well.

However there is still what I call the opcode
issue. Sometimes an enumeration is not just a coding
technique but is a link to the world outside the
program. LOAD has to map to a particular number because it
is hardwired into a microprocessors instruction set or a
particular network protocol.

#define load 42 doesn't get you very far if load is
both an opcode and a protocol command and mapped to
different numbers. I take that as the clue as to the right
way to think about this problem. You should write your
program using the symbol LOAD, and have coding and decoding
routines

(assemble 'load) => 42
(assemble 42) => load
(network 'load) => 561
(network 561) => 'load

Here is a macro that sets it up.  (I've not used it for
real, so there are probably lurking bugs)

Symbols are found from opcodes by using the opcode as an
index into a vector of symbols, so that should be fast,
O(1), though it wastes a lot of space if there are big gaps
in the opcode map.

Opcodes are stored as the machine property of the symbol, so
access should be O(1) also, though it deteriorates linearly
with the depth of overlap of the opcodes of the various different
instruction sets.

Fake Example:

CL-USER> (define-opcodes |8086| (#x3A LDA STA #x4B READ WRITE))
; Converted |8086|.
|8086|
CL-USER> (|8086| 'sta)
59
CL-USER> (|8086| #b01001011)
READ

Code:

(defmacro define-opcodes (machine condensed-list)
  "number mnemonic mnemnonic number ...."
           (let* ((code-assoc (expand condensed-list))
                  (least-opcode (reduce #'min code-assoc :key #'cdr)))
             `(let ((opcode-vector ,(make-decode-table code-assoc least-opcode))
                    (least-opcode ,least-opcode))
               (defun ,machine (thing)
                 (etypecase thing
                   (number (aref opcode-vector (- thing least-opcode)))
                   (symbol (get thing ',machine))))
               ,(cons 'setf
                      (loop for (mnemonic . code) in code-assoc
                            append
                            `((get ',mnemonic ',machine) ,code)))
               ',machine)))

(eval-when (:compile-toplevel :load-toplevel :execute)
  ;; Helper functions for the macro need to be available in
  ;; the compile time environment
(defun expand (list)
           (let ((expansion '())
                 (index 0))
             (dolist (item list expansion)
               (etypecase item
                 (symbol (push (cons item index) expansion)
                         (incf index))
                 ((integer 0) (setf index item))))))

(defun make-decode-table (code-assoc least-opcode)
  (let ((greatest-opcode (reduce #'max
                                 code-assoc
                                 :key #'cdr)))
    (let ((table (make-array (+ 1 (- greatest-opcode
                                     least-opcode)))))
      (loop for (mnemonic . code) in code-assoc
            do (setf (aref table (- code least-opcode))
                     mnemonic))
      table))))

Notice the use of a single polymorphic routine, assemble,
instead of a pair of routines assemble-code,
code-assemble. This is my own idea so it is probably a bad
one, but I cannot work out why just at the moment, sorry.

Alan Crowe
Edinburgh
Scotland
From: Thomas A. Russ
Subject: Re: Enumerations
Date: 
Message-ID: <ymid5hyh9ib.fsf@sevak.isi.edu>
Alan Crowe <····@cawtech.freeserve.co.uk> writes:

> One approach is to go back to first principles and ask why
> is there an issue at all? What is wrong with just using
> symbols?

Hear, hear.  I always thought that enumerations were invented because
the languages in question didn't have real symbols and needed some other
(kludgey) way of letting programmers work with constants that had
meaningful names.

-- 
Thomas A. Russ,  USC/Information Sciences Institute