Thoughts on creating objects in Lisp

If you work with classes in Lisp, you might want to hide the details of creating an object of a class. This practise is generally known as the factory method pattern. I’ll show a very simple example.

Let’s assume, you have defined the following class.

(defclass noun ()
  ((es
    :accessor noun-spanish)
   (es-gender
    :accessor noun-spanish-gender)
   (de
    :accessor noun-german)
   (de-gender
    :accessor noun-german-gender)))

To create an object of this class, you have to call make-instance and setf the attributes (slots). To avoid writing this again and again, you can define the following function.

(defun create-noun (es es-gender de de-gender)
  (let ((n (make-instance 'noun)))
    (progn (setf (noun-spanish n) es)
       (setf (noun-spanish-gender n) es-gender)
       (setf (noun-german n) de)
       (setf (noun-german-gender n) de-gender)
       n)))

You can then use this function to create objects of your class.

CL-USER> (defparameter *noun* (create-noun 'falda 'f 'Rock 'm))
*NOUN*
CL-USER> (noun-german *noun*)
ROCK
CL-USER> (noun-spanish *noun*)
FALDA
CL-USER>

While this is a very simple application of a factory method (or function), you might still find it useful.

Thoughts on creating objects in Lisp

Defining Lisp classes in packages

In Lisp, when you define classes in packages, you have to remember that you have to explicitly export the accessors too. For example, you define the following package with a class inside.

(defpackage :mhn-foo
  (:use :common-lisp)
  (:export #:fooclass))
(in-package :mhn-foo)
(defclass fooclass ()
  ((fooatt
    :accessor fooattribute)))

However, while you can instantiate the class, you get an error message when you try to access the attribute of the class.

CL-USER> (defparameter *fooparameter* (make-instance 'mhn-foo:fooclass))
*FOOPARAMETER*
CL-USER> (setf (mhn-foo:fooattribute *fooparameter*) "foo")
; Evaluation aborted on #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "The symbol ~S is not external in the ~A package." {1005FDFC03}>.

To be able to access the attribute of the class, you have to export it.

(defpackage :mhn-foo
  (:use :common-lisp)
  (:export #:fooclass #:fooattribute))

Now you can work with the attribute of the class without problems.

CL-USER> (defparameter *fooparameter* (make-instance 'mhn-foo:fooclass))
*FOOPARAMETER*
CL-USER> (setf (mhn-foo:fooattribute *fooparameter*) "foo")
"foo"
CL-USER> (mhn-foo:fooattribute *fooparameter*)
"foo"

This is not very different in other programming languages. For example, in Java and C#, you define visibility not only for the class, but for the class members too. If you forget to set visibility for class members, you might have problems accessing them.

Defining Lisp classes in packages

Serializing Lisp lists

In Lisp, it is very easy to serialize and deserialize lists into files. You can use the macros with-open-file and print for this. Using macro with-open-file makes sure that the file is closed automatically even in case of failures. The print macro prints lists in a way that they can be read by the Lisp reader. For example, you define the following function for serialization.

(defun serialize (file-name list)
  (with-open-file (stream file-name :direction :output)
    (print list stream)))

You might want to use keyword argument :if-exists :supersede if you want to overwrite existing files. Add another function for deserialization. It uses the read function to read the list back from the file.

(defun deserialize (file-name)
  (with-open-file (stream file-name :direction :input)
    (read stream)))

Now you can use these two functions to save your lists to file and read them back in. I have defined the two functions in a package called mhn-serial.

CL-USER> (mhn-serial:serialize “~/tmp/list.ser” ‘(1 2 3))
(1 2 3)
CL-USER> (mhn-serial:deserialize “/home/neifer/tmp/list.ser”)
(1 3 4)
CL-USER> (car (mhn-serial:deserialize “/home/neifer/tmp/list.ser”))
1
CL-USER> (cdr (mhn-serial:deserialize “/home/neifer/tmp/list.ser”))
(3 4)
CL-USER> (nth 2 (mhn-serial:deserialize “/home/neifer/tmp/list.ser”))
4
CL-USER> (mhn-serial:deserialize “/home/neifer/tmp/list.ser”)
((1 2 3) (4 5 6))
CL-USER> (nth 1 (car (cdr (mhn-serial:deserialize “/home/neifer/tmp/list.ser”))))
5
CL-USER> 

Please note that I have edited the file between the calls. I have changed the file content from (1 2 3) to (1 3 4) and then to ((1 2 3)(4 5 6)). Just to check if reading works. You could change the content to something more interesting, of course.

CL-USER> (mhn-serial:deserialize “/home/neifer/tmp/list.ser”)
(+ 2 3)
CL-USER> (eval (mhn-serial:deserialize “/home/neifer/tmp/list.ser”))
5
CL-USER> 

You see that you have to be very careful when using eval on objects that you have read from another source. The bad guys could easily compromise your system.

Serializing Lisp lists