From: Tin Gherdanarra
Subject: 60 Lines of generator code -- did I get it right?
Date:
Message-ID: <4cek1kF15p36jU1@individual.net>
Dear cll,
Here is a brief code-sample inspired by Chris Riesbeck's piece on
"generators", to be found here:
http://www.cs.northwestern.edu/academics/courses/325/readings/graham/generators.html
(Tip of the propellor-hat to Zach Beane for the quality link)
I tried to get everything right, but how can I know that I suck?
Comments welcome, especially negative ones. Thanks for any support.
------8<----------- CUT HERE ------------------>8------------
;;; The "Generator Exhausted" token
;;; returned by a generator as soon as
;;; it has hit on the end of the
;;; series.
(defun dumy-end-of-gen nil)
;;; The generator base class
(defclass generator nil
; stores what the generator "is made of"
; i.e. its parameter(s)
((params :accessor generator-params
:initarg :params
:initform nil)
; cache for the current, i.e. last object fetched.
(this-obj :accessor generator-this-obj
:initarg :this-obj
:initform 0)))
;;; The three generator methods:
;;; along the lines of
;;; *i == EOF? --> returns t or nil
;;; *i --> returns two values: the
;;; current item and t or nil upon end or not
;;; *i++ --> returns the current item and t or nil upon end or not
;;; gen-get and gen-next return #'dumy-end-of-gen for the
;;; current item if the generator has been exhausted
;;; have we hit the end yet? Override for you generator
(defgeneric gen-endp (gen))
;;; get the current item in the series, overridable
(defgeneric gen-get (gen))
;;; advance the generator to the next item
(defgeneric gen-next (gen))
;;; overridable for fleshing out your generator
(defgeneric gen-next-core (gen))
;;; The one-size-fits-all gen-next. Do not override
(defmethod gen-next ((gen generator))
(let ((x (gen-get gen))) ; remember what we will return
(if (gen-endp gen) ; report if we are done
(values #'dumy-end-of-gen t)
(progn
(gen-next-core gen) ; advance generator
(values x (gen-endp gen)))))) ; return value, end status
;;;
;;; Let's make a simple generator for numbers
;;; (make-generator-num 100 120) gives you the numbers 100 thru 119
;;; (make-generator-num 100 nil) gives you the nubmers 100 thru infinity
;;;
(defclass generator-num (generator) nil)
;;; A convenience wrapper for setting up a generator-num
;;; /params/ stores what the generator-num is made of
(defun make-generator-num (m &optional n)
(make-instance 'generator-num :params (list m n)))
;;; little helper function doing the actual work
;;; of checking if we've hit the end of the rope
;;; yet
(defun gen-endp-equal (m n)
(and (numberp n) (>= m n)))
;;; our flesh for checking for the generator-num end
(defmethod gen-endp ((gen generator-num))
(gen-endp-equal
(car (generator-params gen))
(cadr (generator-params gen))))
;;; give us the current integer
(defmethod gen-get ((gen generator-num))
(car (generator-params gen)))
;;; Advancing a generator-num, called by
;;; the generic gen-get method
(defmethod gen-next-core ((gen generator-num))
(incf (car (generator-params gen))))
;;;
;;; Let's make a simple generator for lines in
;;; a file. Pass a stream from which to fetch
;;; the lines. This is where the /this-obj/ comes
;;; into play. It stores the line fetched last
;;; for an arbitrary number of calls to
;;; gen-get, without actually accessing the
;;; stream.
;;;
(defclass generator-line (generator) nil)
;;; Convenience wrapper for setting up a generator-line
;;; /params/ stores the stream for the generator-line
(defun make-generator-line (st)
(make-instance 'generator-line :this-obj
(read-line st nil #'dumy-end-of-gen)
:params st))
;;; Our gen-get specialization for the generator-line
(defmethod gen-get ((gen generator-line))
(generator-this-obj gen))
;;; Get us another line, called by the
;;; generic gen-next method. Note that
;;; /read-line/ is instructed to return
;;; a #'dumy-end-of-gen token upon hitting :EOF
(defmethod gen-next-core ((gen generator-line))
(setf (generator-this-obj gen)
(read-line (generator-params gen)
nil
#'dumy-end-of-gen)))
;;; Have we hit the end yet? We know if /read-line/
;;; has handed us an end-of-gen token
(defmethod gen-endp ((gen generator-line))
(or (null (generator-this-obj gen))
(eql #'dumy-end-of-gen
(generator-this-obj gen))))
;;; Go ahead, try it
(defparameter *gn* (make-generator-num 3 7))
(defparameter *gl* (make-generator-line
(open "gens.lisp" :direction :input)))
------8<----------- CUT HERE ------------------>8------------
--
Lisp kann nicht kratzen, denn Lisp ist fluessig