Newsgroups: comp.lang.scheme
Path: cantaloupe.srv.cs.cmu.edu!das-news2.harvard.edu!news2.near.net!howland.reston.ans.net!agate!darkstar.UCSC.EDU!news.hal.COM!decwrl!netcomsv!netcom.com!bakul
From: bakul@netcom.com (Bakul Shah)
Subject: Generators
Message-ID: <bakulD0q2L1.4oE@netcom.com>
Organization: NETCOM On-line Communication Services (408 261-4700 guest)
References: <thinmanD0D8FJ.KEq@netcom.com>
Date: Mon, 12 Dec 1994 23:58:13 GMT
Lines: 223

In a thread on `is the "do" special form available ...'
thinman@netcom.com (Technically Sweet) writes:

>Speaking of which, does anybody have a macro for a less bulky
>iterator than "do"?  (countup (i 0 5 [1]) body), for example?

I will use Lance's question to explore a (hopefully interesting)
tangent :-)

A COUNTUP `generator' building procedure seems to be more useful
than a COUNTUP macro.  Perhaps a more general macro can be built
atop it?  A generator is a parameterless procedure that returns a
new object each time it is called, eventually returning a #f.
Procedure (COUNTUP first last [step]) returns such a generator.
Example:

	(define x (countup 0 4))
	(x) => 0
	(x) => 1
	(x) => 2
	(x) => #f

To run through all possible values returned by a generator (until
we hit #f), we define two procedures:

	(EVERY procedure generator ...)
and
	(ALL procedure generator ...)

In both cases there are N generators and the procedure takes N
arguments.  They both call procedure for every list of values
returned by generators until atleast one generator returns a #f.
EVERY returns the result of the last invocation of procedure (or
#f if it was never called).  ALL returns all the results in a
list.

	(define (square x) (* x x))
	;; generate a list of five squares
	(all square (countup 0 5)) => (0 1 4 9 16)

	;; compute five squares and return the last one.
	(every square (countup 0 5)) => 16

	(all * (countup 0 5) (countdown 0 -5)) => (0 -1 -4 -9 -16)

We add another generator builder EACH:

	(EACH procedure generator ...)

Arguments to EACH are similar to EVERY and ALL but the difference here
is a that the new value generated is the result of the procedure.

	(define (_ x) x) ;; just return the arg.

	;; another way to generate a list of five squares
	(all _ (each square (countup 0 5))) => (0 1 4 9 16)

	;; sqrt of sum of squares
	(all sqrt (each + (each square (countup 0 5))
			  (each square (countup 0 5)))) => (0 1 2 3 4)

	;; a list of lists
	(all (lambda (x) (all _ (countup 0 x))) (countup 0 5)) =>
		(() (0) (0 1) (0 1 2) (0 1 2 3) (0 1 2 3 4))

Note that the way ALL and EVERY are defined, they will terminate
even if you can use infinite stream generators as long as at
least one generator returns a finite number of values.  Also note
an anomaly: a generator can not return #f except to indicate it
is done.

Given these procedures, we can define a generic macro FOR
in terms of EVERY:

	(FOR var generator body)
and/or
	(FOR ((var generator) (var generator) ...) body)

Example:
	(define x 0)
	(for n (countup 0 100) (set! x (+ x y)))
	x => 4950

Macro FOR-MAP can be defined in terms of ALL to return a list of
results.

	(for-map i (countup 0 5)
	    (for-map j (countup 0 5) (cons i j))) =>
		(((0 0) (0 1) (0 2) (0 3))
		 ((1 0) (1 1) (1 2) (1 3))
		 ((2 0) (2 1) (2 2) (2 3))
		 ((3 0) (3 1) (3 2) (3 3))

Procedures COUNTUP, COUNTDOWN, EACH EVERY and ALL are included at
the end of this message.  I've verified they run under SCM.
Implementation of FOR  & FOR-MAP left as an exercise to the
reader!

These procedures + procedures operating on regular expressions
and strings or files can make a good set of tools for text
processing.  For instance, we can define

	(MATCH RE string)
	(NONMATCH RE string)

These return generators that return the next matching or
nonmatching piece respectively.  RE is an extended regular
expression (roughly, any pattern accepted by egrep).  Example:

	;; work on paragraphs of input
	;; paragraphs are separated by a blank line.
	(for paragraph (nonmatch "\n\n" input) (work line))

	;; work on input line fields, separated by whitespace
	(for line (nonmatch "\n" input)
	    (let ((fields (all (nonmatch "[ \t]*" line))))
		(work fields)))

This is still no where near as convinient as awk but I'd like to
see just how far one can go using this approach (i.e. by defining
generally useful procedures rather than emulating awk).  More on
text processing procedures some other time....

Bakul Shah
<bakul@netcom.com> or <bvs@BitBlocks.com>

;;; icon.scm -- *** UNDER CONSTRUCTION ***
;;
;; Copyright (c) 1994, Bakul Shah <bvs@BitBlocks.com>
;; All Rights Reserved.
;;
;; Permission to use, copy, modify, distribute or sell this software and
;; its documentation for any purpose is hereby granted without fee, subject
;; to the following conditions.
;;
;; 1. The above copyright, this list of conditions and the following
;;    disclaimer must appear UNCHANGED in all copies of the software and
;;    related documentation, and their derivative works or modified
;;    versions
;;
;; 2. Binary distribution must be accompanied by documentation that
;;    reproduces the above copyright, this list of conditions and the
;;    following disclaimer.
;;
;; 3. Any modifications to the source code must be clearly marked as such.
;;
;; 4. The author's name may not be used to endorse or promote products
;;    derived from this software without specific prior written permission.
;;
;; This software is provided by the author AS IS.  The author DISCLAIMS
;; any and all warranties of merchantability and fitness for a particular
;; purpose.  In NO event shall the author be LIABLE for any damages
;; whatsoever arising in any way out of the use of this software.
;;

;;
;; $Id: icon.scm,v 1.3 1994/12/12 23:38:49 bvs Exp bvs $
;;
;; bvs 941212 -- original version
;;

;; return a new generator that returns the result of proc.
;; with one arg from each generator.  The new generator returns #f
;; if any argument generaor returns #f.
(define (EACH procedure . generators)
    (lambda ()
	(let loop ((g generators) (r ()))
	    (if (null? g)
		(apply procedure (reverse r))
		(let ((v ((car g)))) (if v (loop (cdr g) (cons v r)) #f))))))

;; 
;; invoke proc. for generated values (one from each generator)
;; and returns the last result.
;; 
(define (EVERY procedure . generators)
    (let ((generator (apply each (cons procedure generators))))
	(let loop ((value (generator)) (result #f))
	    (if value
		(loop (generator) value)
		result))))

;; 
;; invoke proc. for generated values (one from each generator)
;; and returns the list of results.
;; 
(define (ALL procedure . generators)
    (let ((generator (apply each (cons procedure generators))))
	(let loop ((value (generator)) (result ()))
	    (if value
		(loop (generator) (cons value result))
		(reverse result)))))

;; 
;; return a generator that
;; returns first, first+step, first+2*step ... until first >= last
;; step defaults to 1
;; 
(define (COUNTUP first last . rest)
    (let ((step (if (null? rest) 1 (car rest))))
	(lambda ()
	    (if (< first last)
		(let ((result first)) (set! first (+ first step)) result)
		#f))))

;; 
;; return a generator that
;; returns first, first-step, first-2*step ... until first <= last
;; step defaults to 1
;; 
(define (COUNTDOWN first last . rest)
    (let ((step (if (null? rest) 1 (car rest))))
	(lambda ()
	    (if (> first last)
		(let ((result first)) (set! first (- first step)) result)
		#f))))

;; 
;; Return a generator that returns list elements one at a time.
;; 
(define (LIST-EACH l)
    (lambda ()
	(if (null? l) #f (let ((h (car l))) (set! l (cdr l)) h))))
