Hello Marc & co,
My reply is a bit long, but has two parts, a first about the |&| symbol, and a second about my ways to do "printf debugging". I care about the former part more, so if you skip parts of this message, please do so with the second.
Marc Feeley wrote:
Perhaps I shouldn't change pp and instead use a different short name, but which one? It could be a special symbol, for example:
(define (square x) (& x result: (* x x)))
I'm hoping that you won't use |&| for that, since I've long been using this symbol as a shortcut for creating a thunk.
(define-macro (& first . rest) `(lambda() ,first ,@rest))
The & seemed like the best character to me for that purpose;
- in Perl, it's the sigil for subroutine slots in the toplevel - in C, it's taking a pointer - in sh/bash, you use it for suspension of jobs
Ok, those are all not really the same thing. But still related to "suspending execution", to "taking a pointer to something" (give a pointer to code instead of executing it and giving the result); and Perl decided to designate the & for routines, so why shouldn't I do so too (regardless of Perl being ugly and possibly evil).
I've already started using a & as appendix to macros whose ~sole purpose is taking a thunk of their body; like
(define-macro (with-output-to-string& . body) `(with-output-to-string '() (lambda () ;; could have used & here of course, too ,@body)))
But special symbols are in short supply and best left for future extensions and user needs. That's why I like pp. It is easy to type and the meaning is clear.
What do you think?
I don't think I would ever have been bitten by pp not being a procedure, so I won't really care, but I'm not sure of the benefit either; |error| isn't a special form either, and there you've got some machinery for dealing with multiple values already. If you think you want to improve pp, you should improve error as well, or at least make their behaviour of creating the message string match. Now I think making error a macro would really not such a good idea, so I think the non-dieing counterpart of |error| should not be one either. But I've already choosen to call that counterpart something else already: warn. And for the purpuse of displaying a value while returning it, I'm using a procedure I'm calling pp-through.
Here they are:
(define no-value (gensym))
(define-bothtimes (pp-through a #!optional (b no-value)) (define port (current-output-port)) ;; hm ugly, during load, chjmodule outputs to stdout, so this is the ;; relevant port to print to. using current-error-port doesn't work ;; as intended. (if (> (output-port-column port) 1) (newline port)) (if (eq? b no-value) (begin (pp a port) a) (begin (display a port) (pp b port) b)))
(define (warn . l) ;; made it so that it acts the same as gambit's error (if (pair? l) (let ((p (current-error-port))) (display (car l) p) (let loop ((l (cdr l))) (if (pair? l) (begin (write-char #\space p) (write (car l) p) (loop (cdr l))) (newline p)))) (error "cj-env#warn called without arguments")))
(I must admit that I don't know anymore why I did put that "hm ugly" comment above.)
Here they are in action:
(list result: (warn "Hello" "World"))
Hello "World" (result: #!void)
(list result: (let ((i 1)(res '(4 3 2))) (warn "hey my values are:" i: i
res: res))) hey my values are: i: 1 res: (4 3 2) (result: #!void)
and
(list result: (pp-through '(hey "ya")))
(hey "ya") (result: (hey "ya"))
(list result: (pp-through foo: '(hey "ya")))
foo:(hey "ya") (result: (hey "ya"))
(list result: (pp-through "foo:" '(hey "ya")))
foo:(hey "ya") (result: (hey "ya"))
(I'm not sure why I don't emit a space after the first argument in pp-through, it would be consistent with |warn| to do so; but I'm using the two-argument form rarely anyway.)
I'm using |pp-through| for those cases where I want to show a value being returned (optionally with a description before it); that's especially the case in macros to show the code being generated.
I'm using |warn| for those "printf debugging" cases where I want to show some statement or one or more values as a side effect -- if this happens to be in a non-sequence position, this would need me to wrap it with a begin, like:
(let lp ((i 10) (res '())) (if (negative? i) i (begin (warn "in lp" i: i res: res) (lp (- i 1) (cons i res)))))
BTW note that in a case like this the warn couldn't be in return position anyway, so a "wrap-a-function-around" approach like pp-through wouldn't work. (Could you do it with a macro/special form? Not sensibly I think.)
For me that distinction between multi-value-print-capability-but- no-return-value and only-one-value-but-return-it works well enough. (The first case returning #!void may actually be a hint for cases where a warn statement is in a return position by accident.)
The only incentive to turn |warn| into a macro for me had been to automatically also show the source code position of the warn statement, like perl's warn does (unless perl's warn is given a message which ends in a newline, which suppresses the line number / file display), but I've not (yet) bothered enough to do that. And possibly it would be better done by inspecting the continuation of the warn call dynamically, not by making it syntax -- as long as warn is not in tail position (which it never should be) that would work, after all (and Perl did choose that dynamic approach, too, although since Perl doesn't have TCO, there's never a case where finding the location would fail).
What I can't do cleanly with |warn| (but neither can I do it with |error|) is putting some text later in the message, like:
(warn "Foo is" foo "while bar is actually" bar)
will output the third argument as quoted string, of course. So maybe taking this further, inevitably by making it a special form/macro, would be worthwhile. Maybe call it |warn&| then, and also offer an analogous |error&| form? I don't see that problem in the return-the-value case since there you usually don't have the need to put anything *after* the value. The only thing a macro would bring you in that latter case is to show the code which generated the value automatically, but if that code is a big form (as opposed to just a variable reference), it would clutter up the output.
Christian.