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
(:prep
(require "comm"))
(:import-from #:comm
#:open-tcp-stream)
(:import-from #:system
#:wait-for-input-streams)
(:export #:open-tcp-stream
#:wait-for-input-streams
#:lisp))
(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)))
#+lispworks
(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?