lazy string-append and HTML generation oleg@xxxxxx (09 Feb 2000 20:50 UTC)
lazy string-append and HTML generation Shriram Krishnamurthi (09 Feb 2000 20:59 UTC)

lazy string-append and HTML generation oleg@xxxxxx 09 Feb 2000 20:47 UTC

	Indeed, instead of applying string-append to a list of
strings, one can "assume" string-append and leave the list as it
is. If we need to append more strings, we can concatenate the
corresponding lists. Better yet, rather than (append l1 l2) we can
(list l1 l2), thus building a tree. When we're completely done,
chances are we don't need any string-append. We can walk the whole
tree and 'display' in-order.

The following is an elaboration of this idea (using Shriram's
example and making it more contrived):

(SRV:send-reply
  (p
    (table
      (tr (td "Talks ") (td  " = ") (td  " slides + transitions"))
      newline		; note, this is a procedure!
      (and (positive? no-slides)  ; Note, #f is allowed and ignored
        (tr (td) (td "there are " no-slides " slides") (td)))
      (list)		; an empty list is allowed and ignored
			; Note how Scheme comments are allowed in this
			; "markup"
)))

where:
  (define (p . x) (list "<P>" x "</P>"))
  (define (table . x) (list "<TABLE>" x "</TABLE>"))
  (define (tr . x) (list "<TR>" x "</TR>"))
  (define (td . x) (list "<TD>" x "</TD>"))

the definitions above are so regular that they positively
invite macrofication.

By default, SRV:send-reply (see appendix below) writes onto the
standard input. However, if the current output port happens to be a
string port, SRV:send-reply will act as a string-append, or
string-concat. However, unlike regular string-append, SRV:send-reply
handles a tree rather than a flat list. It allows and forces
"promises" (procedural values), it deals intelligently with #f and
'(), it handles characters and numbers in addition to strings. This
suggests that proposals to extend string-append to take characters,
for example, might not be necessary.

	I use the style above often in my code. Sometimes the whole
module is a single application of SRV:send-reply to a large collection
of lists, strings, procedure invocations, procedures, etc. This is
very efficient as each atom is touched exactly twice (once when it is
being put into a cell and another when it is being pulled for
display). All actions (displaying, appending, splicing, flattening)
are being delayed till the very end (when many of them may turn
unnecessary). There is no intermediate garbage created.

	This is very similar to a monadic style typical of
Haskell. One can consider SRV:send-reply being "main", a 'list' a
monadic constructor that _denotes_ a string-append action (without
actually executing it), and "bind" is, well, 'list' again. As we're
dealing with output, all the monads in question are of a type IO ().

	Definitions of procedures 'p', 'table', etc. can _easily_ be
generalized to accept attributes. One can implement any style one
prefers:
	(p (('align "CENTER")) "text" text")
or
	(p (attr align "CENTER") (attr class "myclass") "text" text")
or
	(p align: "CENTER" class: "myclass" "text" text")

	I'm afraid this really has nothing to do with SRFI-13. I'm
grateful to Olin that he tolerated this side discussion for as long as
he did. If someone is interested, we can continue on comp.lang.scheme
or privately. As to SRFI-13: no, I don't propose to add
SRV:send-reply. However, its mere existence and simplicity shows that
one doesn't probably need string-concat or generalized
string-append. SRFI-13 looks great as it is.

	SRV:send-reply _might_ be generalized to a "string output"
SRFI (similar to a future string input SRFI Olin has alluded to). We
can collaborate on elaborating this style (or elaborate on
collaborating). Also, W3C has just recommended XHTML, HTML 4.0
expressed in XML. As many people are doing their own HTML processing
in Scheme, I wonder if there might be an interest in a unified
"(HTML)" specification.

Appendix:

		; Output the 'fragments'
		; The fragments are a list of strings, characters,
		; numbers, thunks, #f -- and other fragments.
		; The function traverses the tree inorder, writes out
		; strings and characters, executes thunks, and ignores
		; #f and '()
		; The function returns #t if anything was written at all;
		; otherwise the result is #f
(define (SRV:send-reply . fragments)
  (let loop ((fragments fragments) (result #f))
    (cond
      ((null? fragments) result)
      ((not (car fragments)) (loop (cdr fragments) result))
      ((null? (car fragments)) (loop (cdr fragments) result))
      ((pair? (car fragments))
        (loop (cdr fragments) (loop (car fragments) result)))
      ((procedure? (car fragments))
        ((car fragments))
        (loop (cdr fragments) #t))
      (else
        (display (car fragments))
        (loop (cdr fragments) #t)))))