Subject: Re: Style question
From: rpw3@rpw3.org (Rob Warnock)
Date: Fri, 09 Dec 2005 07:33:17 -0600
Newsgroups: comp.lang.lisp
Message-ID: <vtudnVIsBNMAGgTenZ2dnUVZ_sCdnZ2d@speakeasy.net>
Steven E. Harris <seh@panix.com> wrote:
+---------------
| rpw3@rpw3.org (Rob Warnock) writes:
| > (loop for line = (read-line s nil nil)
| >       while line
| >       collect line into lines
| >       counting t into line-count
| >       finally (return (values lines line-count)))
| 
| I looked at the macro expansion for "counting t into line-count" and
| noticed that each increment operation is wrapped in a conditional
| "(when t ...)". Does this conditional check get optimized away?
+---------------

I suspect so, in any decent compiler at least. In CMUCL, for example:

    > (macroexpand '(when t (foo)))
    (IF T (PROGN NIL (FOO)) (COND))
    > 

Now that's a *lot* of needless noise, eh? But when we compile it,
it all seems to disappear, and the whole thing gets converted into
an unconditional tail-call of FOO:

    > (disassemble (compile nil (lambda () (when t (foo)))))
    ...
    48910FF8:       .ENTRY "LAMBDA NIL"()     ; (FUNCTION NIL *)
	1010:       POP     DWORD PTR [EBP-8]
	1013:       LEA     ESP, [EBP-32]

	1016:       TEST    ECX, ECX          ; [:NON-LOCAL-ENTRY]
	1018:       JNE     L0

	101A:       MOV     EAX, [#x48910FF0] ; #<FDEFINITION object for FOO>
					      ; No-arg-parsing entry point
					      ; [:NON-LOCAL-ENTRY]
	1020:       XOR     ECX, ECX
	1022:       PUSH    DWORD PTR [EBP-8]
    ;;; [3] (FOO)
	1025:       JMP     DWORD PTR [EAX+5] ; [:CALL-SITE]
	1028: L0:   BREAK   10                ; Error trap
	102A:       BYTE    #x02
	102B:       BYTE    #x19              ; INVALID-ARGUMENT-COUNT-ERROR
	102C:       BYTE    #x4D              ; ECX
    > 

+---------------
| I'm just wondering why you wouldn't prefer the following:
|   (loop for line = (read-line s nil nil)
|         for line-count upfrom 0
|         while line
|         collect line into lines
|         finally (return (values lines line-count)))
+---------------

(*blush!*) No special reason. Of course you're right, "LINE-COUNT
UPFROM 0" is fine, and I'll probably even change my library routine
accordingly. The "COUNTING T" is there probably because I learned
COUNTING before I learned multiple FORs. On the other hand, it
might have been because initially I was unsure about the order of
side-effects with multiple loop variable in the presence of a WHILE,
so I put the COUNTING *after* the WHILE to make sure it only counted
when a line was read. But now that I know LOOP a little better, I
know that all FORs must be before any WHILEs, and are executed in
order.

In CL, but *especially* in LOOP, follow the "there's more than one
way to do it" rule. I suspect I'm not the only one who tends to
get locked into whatever idiom one discovers first. I've only been
seriuoaly using CL for 4-5 years, and even code I wrote just a year
ago now looks horribly non-idiomatic. [But who has time for style-only
rewrites...?]

By the way, since there are no serial dependencies between the two
loop variables, I would prefer to use AND instead of the second FOR,
which might generate slightly better code in some cases:

    (loop for line = (read-line s nil nil)
	  and line-count upfrom 0
	  while line
      collect line into lines
      finally (return (values lines line-count)))

Using FOR/AND/... except when you *need* FOR/FOR/... is, to me,
analogous to always using LET except when you *need* LET*.
Some people feel that matters; some don't.


-Rob

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607