October 9th, 2008

quicknet: a proof of concept file

I'm trying to make a single-file, cross-implementation program that involves some networking. I only need a handful of relatively high-level functions: connect to a port on a host, write octets, read available octets, and disconnect.

Most similar attempts I've seen involve functions with a bunch of intermixed #+foo and #-bar, and I find that kind of ugly. It's a little less ugly to have something approximately like #+foo (load "foo-defs.lisp"), but that defeats my single-file goal.

I decided to give it a try with CLOS, using an object to represent the current implementation and defining methods that specialize on the implementation class. For example, here's part of the file to support LispWorks:

;;; LispWorks

(define-implementation-package :lispworks #:qn.lw
   (require "comm"))
  (:import-from #:comm
  (:import-from #:system
  (:export #:open-tcp-stream

(defclass qn.lw:lisp (lisp connections-are-streams) ())

(defmethod %open-connection (host port (lisp qn.lw:lisp))
  (qn.lw:open-tcp-stream host port
                          :direction :io
                          :read-timeout 0
                          :element-type 'octet))

(defmethod %read-octets :before (buffer connection (lisp qn.lw:lisp))
  (declare (ignore buffer))
  (qn.lw:wait-for-input-streams (list connection)))

(setf *lisp* (make-instance 'qn.lw:lisp))

I split a subset of the program into its own file with just the networking stuff: quicknet.lisp. It supports SBCL, CLISP, LispWorks, Allegro Common Lisp, ECL, and Clozure CL.

I'd love to get some feedback. What do you think of the approach? Is there something I should do differently or better?