From: Erann Gat
Subject: The Right Way to process streams, strings, etc. etc.
Date: 
Message-ID: <gat-3007031737290001@k-137-79-50-101.jpl.nasa.gov>
The recent thread on translating strings made me give some though to the
Right Way to do these things, and I've decided that the Right Way is to
generalize the functionality of the MAP function so that it works for
streams as well as sequences, e.g.:

(defmethod gmap (type f (s sequence)) (map type f s))

(defmethod gmap (type f (s stream))
  (map type f (read-all-characters-from-stream s)))

Of course, you'd want a more efficient version that didn't actually read
the entire contents of the stream into memory, e.g.:

(defun stream-char-generator (s)
  (fn () (or (read-char s nil nil) (values nil t))))

(defun walk-generator (gen f)
  (loop
    (receive (elt done?) (funcall gen)
      (if done? (return nil))
      (funcall f elt))))

(defmethod gmap (type f (s stream))
  (gmap type f (stream-char-generator s)))

(defmethod gmap (type f (g function))
  (cond
   ( (null type) (walk-generator g f) )
   ( (subtypep type 'list)
     (with-collector collect
       (walk-generator g (fn (c) (collect (funcall f c))))) )
   ( (subtypep type 'string)
     (with-output-to-string (out)
       (walk-generator g (fn (c) (princ (funcall f c) out)))) )
   ( (subtypep type 'vector)
     (let ( (v (make-array 0 :adjustable t :fill-pointer t)) )
       (walk-generator g (fn (c) (vector-push-extend c v)))
       v) )
   (t (error "~S is not a valid output specifier for ->" type))))

Now you can do:

? (gmap nil 'print "foo")

#\f 
#\o 
#\o 
NIL
? (gmap nil 'print (make-string-input-stream "foo"))

#\f 
#\o 
#\o 
NIL
? 

You can even walk over stream entities other than characters:

(defun stream-line-generator (s)
  (fn () (or (read-line s nil nil) (values nil t))))

? (gmap 'list 'identity
        (stream-line-generator
         (make-string-input-stream "foo
baz
bar")))
("foo" "baz" "bar")

And all this in under thirty minutes.  Damn, Lisp is cool!

E.

P.S.  To run this code you'll also need:

(defmacro fn (args &body body) `(lambda ,args ,@body))

I think that's the only one of my private macros that's still in this code.

From: Janis Dzerins
Subject: Re: The Right Way to process streams, strings, etc. etc.
Date: 
Message-ID: <twk65ljktt4.fsf@gulbis.latnet.lv>
···@jpl.nasa.gov (Erann Gat) writes:

> P.S.  To run this code you'll also need:
> 
> (defmacro fn (args &body body) `(lambda ,args ,@body))
> 
> I think that's the only one of my private macros that's still in this code.

I spotted you wrote RECEIVE instead of MULTIPLE-VALUE-BIND.

-- 
Janis Dzerins

  If million people say a stupid thing, it's still a stupid thing.
From: Erann Gat
Subject: Re: The Right Way to process streams, strings, etc. etc.
Date: 
Message-ID: <gat-3107030805120001@192.168.1.51>
In article <···············@gulbis.latnet.lv>, Janis Dzerins
<·····@latnet.lv> wrote:

> ···@jpl.nasa.gov (Erann Gat) writes:
> 
> > P.S.  To run this code you'll also need:
> > 
> > (defmacro fn (args &body body) `(lambda ,args ,@body))
> > 
> > I think that's the only one of my private macros that's still in this code.
> 
> I spotted you wrote RECEIVE instead of MULTIPLE-VALUE-BIND.

Ah, sorry:

(defmacro receive (vars form &body body)
  `(multiple-value-bind ,vars ,form ,@body))

E.