I'm trying to learn some Lisp (Common Lisp) lately, and I wonder if there is a way to give constant numbers a name just like you can do in C via enums.
I don't need the full featureset of enums. In the end I just want to have fast and readable code.
I've tried globals and little functions, but that always came with a degration in performance. Just plugging the numbers into the code was always faster.
-
Enums are redundant for Lisp, the reason being that all symbols are their own identity, so you can just use those, for example:
[dsm@localhost:~]$ clisp -q [1]> (setf x 'some) ;' SOME [2]> (eq x 'some) ;' T [3]> -
The normal way to do enumerations in Lisp is to use symbols. Symbols get interned (replaced with pointers to their entries in a symbol table) so they are as fast as integers and as readable as enumerated constants in other languages.
So where in C you might write:
enum { apple, orange, banana, };In Lisp you can just use
'apple,'orangeand'bananadirectly.If you need an enumerated type, then you can define one with
deftype:(deftype fruit () '(member apple orange banana))
and then you can use the type
fruitindeclare,typep,typecaseand so on,and you can write generic functions that specialize on that type.kmkaplan : Yes, but better use keywords.Gareth Rees : That's a good point: by using keyword you avoid having to worry about which package your symbols are in.Matthias Benkard : Actually, generic functions can only specialise on classes, not arbitrary types.Rainer Joswig : But you can write methods for symbols. (defmethod draw-line (x y (style (eql :dashed))) ...)Mike Weller : What if you want slightly stronger typing? I want to make sure I don't make a typo which won't be detected until runtime? I'm not really a lisp guy, and I know it isn't a strongly typed language, just curious.Ken : Michael: then you do what you do anywhere -- write a unit test. There will always be typos your compiler can't catch (the sign of an int, inequality operators, string literals, etc.). :-) Really, of all the bugs I've ever had in Lisp code, a vanishingly small fraction were type errors. -
For example you want to name font sizes:
(defconstant +large+ 3) (defconstant +medium+ 2) (defconstant +small+ 1)You could write a macro to make that shorter.
Above constant definitions are usually written ONLY when these numbers need to be passed to some external non-Lisp code.
Otherwise one would just use keyword symbols: :large, :medium and :small.
You can test them with EQ and everything that uses some test for equality.
(let ((size :medium)) (ecase size (:small ...) (:medium ...) (:large ...)))You can also write methods for it:
(defmethod draw-string (message x y (size (eql :large))) ...)As mentioned you could define a set type:
(deftype size () '(member :small :medium :large))Then you can check if something is either of those:
(let ((my-size :medium)) (check-type my-size size))Above would signal an error if my-size is not one of :small, :medium or :large.
You can also use the type in a defclass form:
(defclass vehicle () ((width :type size :initarg :width)))Now you would create objects like here:
(make-instance 'vehicle :width :large)Some Common Lisp implementations will check when you set the slot to some illegal value.
If you now create objects of class vehicle, the slots will be one of :large, :medium or :small. If you look at the object in a debugger, inspector or some other tool you will see the symbolic names and not 1, 2 or 3 (or whatever values you would normally use).
This is part of the Lisp style: use symbolic names when possible. Use symbols with number values only in interface code to foreign functions (like calling external C code that uses enums).
0 comments:
Post a Comment