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
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.
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
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/
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
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
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
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
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
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