20140101

Enumerated Types in CL


The Holidays gave me some time to go back to work on another of my little libraries. Here is the result.


Enumerated Types in Common Lisp


Several programming languages have the notion of enumerated type, e.g., C/C++ enum. Common Lisp can substitute the notion of enumerated type using the member type specifiers.

    (deftype colors () '(member red green blue))

This is sufficient for the annotation purposes of traditional Common Lisp, but it falls short of the standard uses that are expected of enumerated types in C/C++ and other languages. Moreover, languages like Java now provide very structured enumerated types.
Consider the following Java enumerated type, which provides an idiomatic way to express certain language processing functionalities:

    public enum Operation {
      PLUS   { double eval(double x, double y) { return x + y; } },
      MINUS  { double eval(double x, double y) { return x - y; } },
      TIMES  { double eval(double x, double y) { return x * y; } },
      DIVIDE { double eval(double x, double y) { return x / y; } };

      // Do arithmetic op represented by this constant
      abstract double eval(double x, double y);
    }

It is not difficult to emulate such methods in Common Lisp using EQL specializers and using symbols as enumerated type tags.
On the other hand, C/C++ use of enumerated types as numeric integer constants, as in

    enum numbers {ONE,
                  TWO,
                  FORTY_TWO = 42,
                  FORTY_THREE
    };

can easily be emulated in Common Lisp by using appropriate defconstant's; alas, this breaks the use of case and similar constructs.
Common Lisp programmers have, needless to say, come up with several versions of enumerated types. Most available definitions provide a DEFENUM or DEF-ENUM macro which provides functionalities similar to C/C++ enumerated types, while adding a switch or select macros as work-around the case problem. A typical rendition of the C/C++ enum just shown could be rendered as

    (def-enum numbers ONE TWO (FORTY-TWO 42) FORTY-THREE)

In the best Common Lisp N.I.H. tradition, yet anothern DEFENUM macro is necessary.


The DEFENUM Library


The present library, unimaginatively named DEFENUM, provides a new DEFENUM macro that merges most of the functionalities provided elsewhere. The objective is, as always, to make the simple things simple and the complex ones possible (and possibly, simple as well).
The simplest enumerated types are represented as expected:

     (defenum season (spring summer fall winter))

This defines an enumerated type (the macro expands - also - into a deftype) with the four tags indicating the seasons. The type checks work as expected, and the C/C++ behavior is also reproduced, as the following shows:

    cl-prompt> (typep 'fall 'season)
    T

    cl-prompt> (+ fall winter)
    5

The library provides also a number of facilities to handle enumerated types.

    cl-prompt> (season-p 'spring)
    T

    cl-prompt> (season-p winter) ; No quote.
    T

The function season accesses the individual tags.

    cl-prompt> (season winter) 
    WINTER 

Tags can be handled as lists.

    cl-prompt> (tags 'season)
    (SPRING SUMMER FALL WINTER)

Other functions are available as well.

    cl-prompt> (loop for season in (tags 'season)
                     for s-id from spring
                     do (format t "~S is followed by ~S~%"
                                season
                                (tag-of 'season (mod (1+ s-id) 4))))
    SPRING is followed by SUMMER
    SUMMER is followed by FALL
    FALL is followed by WINTER
    WINTER is followed by SPRING
    NIL

    cl-prompt> (previous-enum-tag 'season 'fall)
    SUMMER

Enumerated types live in their own namespace.

    cl-prompt> (find-enum 'season)
    #<ENUM SEASON (SPRING SUMMER FALL WINTER)>


Simple and Structured Enumerated Types


The season enumerated type is simple: nothing particularly complicated. On the other hand, Java enumerated types show that is possible to extend the notion of enumerated type in an interesting way, by introducing the notion of structured enumerated type.
Two interesting examples in Java [JE5] are the Planet and the Operation enumerated types.

    public enum Planet {
      MERCURY (3.303e+23, 2.4397e6),
      VENUS   (4.869e+24, 6.0518e6),
      EARTH   (5.976e+24, 6.37814e6),
      MARS    (6.421e+23, 3.3972e6),
      JUPITER (1.9e+27,   7.1492e7),
      SATURN  (5.688e+26, 6.0268e7),
      URANUS  (8.686e+25, 2.5559e7),
      NEPTUNE (1.024e+26, 2.4746e7),
      PLUTO   (1.27e+22,  1.137e6);

      private final double mass;   // in kilograms
      private final double radius; // in meters
      Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
      }
      public double mass()   { return mass; }
      public double radius() { return radius; }

      // universal gravitational constant  (m3 kg-1 s-2)
      public static final double G = 6.67300E-11;

      public double surfaceGravity() {
        return G * mass / (radius * radius);
      }
      public double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
      }
    }

A program that exercises the Planet enumerated type is the following (the units are unimportant):

    $ cat Planet.java
        
    public enum Planet {

        ...

        public static void main(String[] args) {
          double earthWeight = Double.parseDouble(args[0]);
          double mass = earthWeight/EARTH.surfaceGravity();
          for (Planet p : Planet.values())
             System.out.printf("Your weight on %s is %f%n",
                               p, p.surfaceWeight(mass));
        }
    }

    $ java Planet 175
    Your weight on MERCURY is 66.107583
    Your weight on VENUS is 158.374842
    Your weight on EARTH is 175.000000
    Your weight on MARS is 66.279007
    Your weight on JUPITER is 442.847567
    Your weight on SATURN is 186.552719
    Your weight on URANUS is 158.397260
    Your weight on NEPTUNE is 199.207413
    Your weight on PLUTO is 11.703031

DEFENUM provides structured enumerated types as well. The Java Planet enumerated type and the test program above are rendered in Common Lisp as follows:

    (defconstant g 6.67300D-11)

    (defenum (planet (:initargs (mass radius)))
         ((MERCURY (3.303D+23 2.4397D6))
          (VENUS   (4.869D+24 6.0518D6))
          (EARTH   (5.976D+24 6.37814D6))
          (MARS    (6.421D+23 3.3972D6))
          (JUPITER (1.9D+27   7.1492D7))
          (SATURN  (5.688D+26 6.0268D7))
          (URANUS  (8.686D+25 2.5559D7))
          (NEPTUNE (1.024D+26 2.4746D7))
          (PLUTO   (1.27D+22  1.137D6))
          )
         
         ((mass 0.0d0 :type double-float)
          (radius 0.0d0 :type double-float)
          )

         (:documentation "The Planet Enum.")

         (:method surface-gravity ((p planet))
          (* g (/ mass (* radius radius))))

         (:method surface-weight ((p planet) other-mass)
          (* other-mass (surface-gravity p)))
         )

Note the definition of methods that will be specialized on the tags of the enumeration. With the definition above, the test Java program can be rendered, as an example, as follows:

    cl-prompt> (let* ((earth-weight 175)
                      (mass (/ earth-weight (surface-gravity 'earth)))
                      )
                   (dolist (p (tags 'planet))
                     (format t "Your weight on ~S is ~F~%"
                             (tag-name  p)
                             (surface-weight p mass)))

    Your weight on MERCURY is 66.10758266016366
    Your weight on VENUS is 158.37484247218296
    Your weight on EARTH is 174.99999999999997
    Your weight on MARS is 66.27900720649754
    Your weight on JUPITER is 442.84756696175464
    Your weight on SATURN is 186.55271929202414
    Your weight on URANUS is 158.39725989314937
    Your weight on NEPTUNE is 199.20741268219015
    Your weight on PLUTO is 11.703030772485283

tag-name is necessary because the print-object method for structured tags is not too extreme; it could be made to print the tag name.
Of course, all the other functions presented before work as expected.

    cl-prompt> (planet-p 'venus)
    T

    cl-prompt> (planet-p 'vulcan)
    NIL

The Java Operation example shows how to define methods that are actually specialized on tags (the rationale is to avoid writing error-prone non-object-oriented switch statements. The Java Operation is the following:

    public enum Operation {
      PLUS    { double eval(double x, double y) { return x + y; } },
      MINUS   { double eval(double x, double y) { return x - y; } },
      TIMES   { double eval(double x, double y) { return x * y; } },
      DIVIDE  { double eval(double x, double y) { return x / y; } };

      // Do arithmetic op represented by this constant
      abstract double eval(double x, double y);

      public static void main(String args[]) {
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
          System.out.printf("%f %s %f = %f%n", x, op, y, op.eval(x, y));
      }
    }

This form of Java enum type definition allows for the direct association of methods to tags. In Common Lisp this is nothing more than EQL specialized methods, and DEFENUM directly provides for this idiom. Actually there are two equivalent forms for it.

    (defenum operation
         (PLUS MINUS TIMES DIVIDE)
         ()
         (:method evaluate ('plus x y) (+ x y)) ; Note the 'quote' shorthand.
         (:method evaluate ('minus x y) (- x y))
         (:method evaluate ('times x y) (* x y))
         (:method evaluate ('divide x y) (/ x y))
         )

... or, more similar to the original Java idiom ...

    (defenum operation
         ((PLUS () (:method evaluate (x y) (+ x y))) ; Note the 'no tag argument' shorthand.
          (MINUS () (:method evaluate (x y) (- x y)))
          (TIMES () (:method evaluate (x y) (* x y)))
          (DIVIDE () (:method evaluate (x y) (/ x y)))
          ))

The test program works as expected.

    cl-prompt> (let ((x 4) (y 2))
                  (dolist (op (tags 'operation))
                     (format t "~S ~S ~S = ~S~%" x (tag-name op) y (evaluate op x y))))
    4 PLUS 2 = 6
    4 MINUS 2 = 2
    4 TIMES 2 = 8
    4 DIVIDE 2 = 2
    NIL

Therefore DEFENUM allows for all Java enumerated types idioms.


Having Your Cake...


Java enumerated types cannot be used in the way C/C++ enum types are (or, at least, not directly). This is because Java has only structured enumerated types.
DEFENUM lets you eat your cake.
Consider the following (contrived) C++ program.

// rgb.cc --

#include <iostream>

using namespace std;

enum rgb {
  RED   = 0xff0000,
  GREEN = 0x00ff00,
  BLUE  = 0x0000ff
};

int
mix_colors(enum rgb c1, int c2, int c3) { return c1 | c2 | c3; }

int
main() {
  enum rgb basic_color = GREEN;

  cout << "The basic color chosen is: " << basic_color << endl;
  cout << "WHITE is: " << (RED | GREEN | BLUE) << endl;
  cout << "WHITE is also: " << mix_colors(RED, GREEN, BLUE) << endl;
}

// end of file -- rgb.cc --

DEFENUM allows to mix and match simple and structured enumerated types, as the following (again, contrived) example shows.

    cl-prompt> (defenum (colors (:initargs (r g b)))
                  ((red   #xff0000 (255 0 0) (:method mix (c2 c3) (logior red c1 c2)))
                   (green #x00ff00 (0 255 0) (:method mix (c2 c3) (logior green c1 c2)))
                   (blue  #x0000ff (0 0 255) (:method mix (c2 c3) (logior blue c1 c2)))
                   )
                  ((r 0 :type (integer 0 255))
                   (g 0 :type (integer 0 255))
                   (b 0 :type (integer 0 255))
                   )
                  (:documentation "The Colors Enum."))
    #<ENUM COLORS (RED GREEN BLUE)>

    cl-prompt> (format t "The color WHITE is ~D~%" (mix red green blue)) ; Yes, this works as is!
    The color WHITE is 16777215

    cl-prompt> (mapcar #'colors-r (tags 'colors))
    (255 0 0)

The C/C++ style numeric tag can be mixed with structured enumerated types.
Now you can eat your cake.
Not that you have to, but it is nice to know you can.

Final Remarks


The DEFENUM library is inspired by Java 5.0 'enum' classes and it allows you to build both simple and structured enumerated types in Common Lisp. It offers all the facilities of the Java version, while also retaining the C/C++ 'enum tags are integers' feature.
The use of the DEFENUM enumeration types has some limitations, due, of course, to Common Lisp slack typing and a few implementation choices.

References


The usual ones, plus searches of 'common lisp defenum def-enum'.
[JE5] Java Online Documentation, Enums, link.

Project site


DEFENUM is hosted at http://defenum.sourceforge.net.

Enjoy!



(cheers)

20131128

Fixed access to common-lisp.net git repositories.


People pointed out that some git repositories of mine on common-lisp.net were not clonable.  I fixed the problem and now you should be able to clone the repositories for NEW-OP and XHTMΛ.


(cheers)

20131108

He who HEΛPs himself...


Just fixed a few bugs to HEΛP.

  • *Earmuffed* entries are now retrieved correctly; this was an issue with a file transfer to a Windows machine.
  • Package non-external entries are now marked as such in the main documentation page (they are marked "internal").

Thanks to Faré and everybody else who wrote me (publicly or in private) pointing out initial issues.


(cheers)

20131106

With a little HEΛP....



I finally managed to push a preliminary version of my documentation generation system to Sourceforge. The project's site (and the links to the git repository) are at https://sourceforge.net/projects/helambdap/. The project's main web page (obviously self-generated) is http://helambdap.sourceforge.net. the git repository can be found there.
You are all welcome to play around with the code (it has a BSD license) and to suggest (better: provide) improvements.
An example of what HEΛP can do with a third party system is here: the documentation generated for the UIOP portability substrate of ASDF3.
The actual call used to generate UIOP's documentation is below (comments follow).

(document #P"asdf/uiop/"
          :documentation-title "UIOP"
          :everything t
          :exclude-files (list #P"/Path/To/asdf3/asdf/uiop/asdf-driver.asd")
          :special-methods-defs-files (list #P"/This/file/here/helambda-asdf3.lisp")
          )

The document function is called on a folder ("asdf/uiop/") with an obvious title. The other arguments have the following meaning:
  • :everything t tells HEΛP to generate documentation pages for everything; these are stored in the dictionary subfolder with file-names which should be relatively obvious. Ordinarily, HEΛP would generate doc pages only for public (i.e., exported) interfaces.
  • :exclude-files is a list of files to forget about; UIOP has one file with an unqualified DEFSYSTEM in, which confuses HEΛP.
  • special-methods-defs-files is a list of files containing specialized definitions to handle a library extra "definition" forms and macros. UIOP has a number of them.

The results are, IMHO, pretty good. Also note that I used HEΛP on the pure set of UIOP files. The actual intended use of HEΛP is to generate documentation of a loaded (and, possibly, compiled) library or system.

So, give it a spin and let me know what you think. If you want, you can subscribe to the mailing lists at the following link: https://sourceforge.net/p/helambdap/mailman/.

HEΛP depends on XHTMΛ. For the time being you have to fetch them directly from the relative sites.  Eventually they will be submitted to Quicklisp.


(cheers)

20130717

Making life difficult for SLOT-VALUE

In a current thread on c.l.l. there was a discussion about how to "hide" bits and pieces of a data structure (the discussion was really about mutable strings). That got me thinking about how to provide more "hiding" than what you can achieve with the package system and with CLOS.

The following is a cute - I believe - hack to make the life of the slot-value user much more difficult. Remember that slot-value is just a function.

CL-USER 7 > (defclass foo ()
              ((#.(gensym (random 42)) :reader foo-slot :initarg :slot)))
#

CL-USER 8 > (describe (make-instance 'foo))

# is a FOO
G21      #

CL-USER 9 > (describe (make-instance 'foo :slot 42))

# is a FOO
G21      42

CL-USER 10 > (defclass foo ()
               ((#.(gensym (random 42)) :reader foo-slot :initarg :slot)))
#

CL-USER 11 > (describe (make-instance 'foo :slot 42))

# is a FOO
G5      42

So, the trick is to generate random slot names, which can be accessed only via CLOS accessors (readers and writers). Plus, in order to make life difficult for any program "inspecting" the guts of a class, you can just redefine it at random times during your application's lifetime.

Not foolproof, but, as I said, cute.


(cheers)

20130709

A few fixes to XHTMΛ

Just added a few fixes to (X)HTMΛ (thanks to Colin J.E. Lupton for finding the bugs). There may still be an issue with my use of ASDF befor asking for inclusion with Quicklisp. In any case, more cleanup coming up soon.



(Cheers)

20130628

XHTMΛ on common-lisp.net

Finally got around to put XHTMΛ on common-lisp.net.  Check out common-lisp.net/project/xhtmlambda; for the time being, send me an email for instructions about how to get to the git repository.

Enjoy

(cheers)