From: Joost Kremers
Subject: output either to file or stdout
Date: 
Message-ID: <slrngd279p.30s.joostkremers@j.kremers4.news.arnhem.chello.nl>
when i want to write output to a file, i can just use WITH-OPEN-FILE. if i
want to write output to the screen, i use format with T as the DESTINATION
argument.

but what if i want to give the user the option of specifying whether he
wants the output to go to the screen or to a file?

at first, i thought i could write the following macro do solve this:

(defmacro with-output-destination ((var dest &rest rest) &body body)
  (if (eq dest t)
      `(let ((,var t))
	 ,@body)
      `(with-open-file (,var ,dest ,@rest)
	 ,@body)))

but this (obviously) doesn't work if WITH-OUTPUT-DESTINATION is called with
a variable as its destination argument:

(let ((destination t))
  (with-output-destination (out destination)
    (format out "Hello world!")))

i want this to write to stdout, but instead it tries to open a file ~/t.
the problem is of course that when the macro is expanded, the variable DEST
evaluates to the symbol DESTINATION, and it is this symbol that is compared
to T in the IF-clause.

changing the macro to the following works:

(defmacro with-output-destination ((var dest &rest rest) &body body)
  `(if (eq ,dest t)
       (let ((,var t))
	 ,@body)
       (with-open-file (,var ,dest ,@rest)
	 ,@body)))

but i find this quite unpretty, given that now the macro body appears twice
in the expansion.

is there a better way to do this? i can't help but feel that there must be...


-- 
Joost Kremers                                      ············@yahoo.com
Selbst in die Unterwelt dringt durch Spalten Licht
EN:SiS(9)

From: Zach Beane
Subject: Re: output either to file or stdout
Date: 
Message-ID: <m3wshazodc.fsf@unnamed.xach.com>
Joost Kremers <············@yahoo.com> writes:

> changing the macro to the following works:
>
> (defmacro with-output-destination ((var dest &rest rest) &body body)
>   `(if (eq ,dest t)
>        (let ((,var t))
> 	 ,@body)
>        (with-open-file (,var ,dest ,@rest)
> 	 ,@body)))
>
> but i find this quite unpretty, given that now the macro body appears twice
> in the expansion.

Others have made good points about the quality of the the general
idea. But for the specific problem of having the body twice in the
expansion, one option is something like:

  (flet ((,fun (,var)
            ,@body))
    (if (eq ,dest t)
        (,fun ,stream)
        (with-open-file (,stream ...)
           (,fun ,stream))))

Zach
From: Rainer Joswig
Subject: Re: output either to file or stdout
Date: 
Message-ID: <joswig-B2C0BF.18581817092008@news-europe.giganews.com>
In article 
<···························@j.kremers4.news.arnhem.chello.nl>,
 Joost Kremers <············@yahoo.com> wrote:

> when i want to write output to a file, i can just use WITH-OPEN-FILE. if i
> want to write output to the screen, i use format with T as the DESTINATION
> argument.
> 
> but what if i want to give the user the option of specifying whether he
> wants the output to go to the screen or to a file?
> 
> at first, i thought i could write the following macro do solve this:
> 
> (defmacro with-output-destination ((var dest &rest rest) &body body)
>   (if (eq dest t)
>       `(let ((,var t))
> 	 ,@body)
>       `(with-open-file (,var ,dest ,@rest)
> 	 ,@body)))
> 
> but this (obviously) doesn't work if WITH-OUTPUT-DESTINATION is called with
> a variable as its destination argument:
> 
> (let ((destination t))
>   (with-output-destination (out destination)
>     (format out "Hello world!")))
> 
> i want this to write to stdout, but instead it tries to open a file ~/t.
> the problem is of course that when the macro is expanded, the variable DEST
> evaluates to the symbol DESTINATION, and it is this symbol that is compared
> to T in the IF-clause.
> 
> changing the macro to the following works:
> 
> (defmacro with-output-destination ((var dest &rest rest) &body body)
>   `(if (eq ,dest t)
>        (let ((,var t))
> 	 ,@body)
>        (with-open-file (,var ,dest ,@rest)
> 	 ,@body)))
> 
> but i find this quite unpretty, given that now the macro body appears twice
> in the expansion.
> 
> is there a better way to do this? i can't help but feel that there must be...

Look for example at the function PRINC. It takes a stream as an argument:

(princ "foo" stream)

STREAM is a variable. You can set it or bind it to any stream 
you want. The standard output stream is the value of *standard-output*.
You can change the value of that variable, too.

(defun my-printing-function (something some-stream)
  ...
  (princ something some-stream)
  ...)

Now you can call the function with a stream:


(with-open-file (foo "foo.lisp" :direction :output)
  (my-printing-function "foo something" foo))

Or:

(my-printing-function "foo something" *standard-output*)

Or:

(let ((*standard-output* (get-some-stream)))
  (my-printing-function "foo something" *standard-output*))

Since PRINC writes by default to *standard-output*,
you can also do:


(defun my-printing-function (something)
  ...
  (princ something)
  ...)

Or


(defun my-printing-function (something &optional (stream *standard-output*)
  ...
  (princ something stream)
  ...)

Usually don't use T. At least understand that T usually means
'use the value of *TERMINAL-IO*' (which is a bidirectional stream).

-- 
http://lispm.dyndns.org/
From: Pascal J. Bourguignon
Subject: Re: output either to file or stdout
Date: 
Message-ID: <87wshaae2q.fsf@hubble.informatimago.com>
Joost Kremers <············@yahoo.com> writes:
>
> changing the macro to the following works:
>
> (defmacro with-output-destination ((var dest &rest rest) &body body)
>   `(if (eq ,dest t)
>        (let ((,var t))
> 	 ,@body)
>        (with-open-file (,var ,dest ,@rest)
> 	 ,@body)))
>
> but i find this quite unpretty, given that now the macro body appears twice
> in the expansion.
>
> is there a better way to do this? i can't help but feel that there must be...

Yes, the best way, as indicated by the other answers is to use a
special variable, such as *standard-output*.


Otherwise, to answer the question, you can factor out the body with a
function:

(defmacro with-output-destination ((var dest &rest rest) &body body)
  (let ((nody (gensym))
        (vest (gensym)))
    `(flet ((,nody (,var) ,@body))
       (let ((,vest ,dest))
         (if (eq ,vest t)
             (,nody t)
             (with-open-file (,var ,vest ,@rest)
               (,nody ,var)))))))


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

NOTE: The most fundamental particles in this product are held
together by a "gluing" force about which little is currently known
and whose adhesive power can therefore not be permanently
guaranteed.
From: Joost Kremers
Subject: Re: output either to file or stdout
Date: 
Message-ID: <slrngd6vel.30u.joostkremers@j.kremers4.news.arnhem.chello.nl>
Pascal J. Bourguignon wrote:
> Otherwise, to answer the question, you can factor out the body with a
> function:
>
> (defmacro with-output-destination ((var dest &rest rest) &body body)
>   (let ((nody (gensym))
>         (vest (gensym)))
>     `(flet ((,nody (,var) ,@body))
>        (let ((,vest ,dest))
>          (if (eq ,vest t)
>              (,nody t)
>              (with-open-file (,var ,vest ,@rest)
>                (,nody ,var)))))))

very interesting, i wouldn't have thought of that myself... thanks!


-- 
Joost Kremers                                      ············@yahoo.com
Selbst in die Unterwelt dringt durch Spalten Licht
EN:SiS(9)
From: Madhu
Subject: Re: output either to file or stdout
Date: 
Message-ID: <m33ajybxmn.fsf@moon.robolove.meer.net>
* Joost Kremers <···························@j.kremers4.news.arnhem.chello.nl> :
Wrote on 17 Sep 2008 15:09:11 GMT:

| when i want to write output to a file, i can just use WITH-OPEN-FILE. if i
| want to write output to the screen, i use format with T as the DESTINATION
| argument.
|
| but what if i want to give the user the option of specifying whether he
| wants the output to go to the screen or to a file?
|
| at first, i thought i could write the following macro do solve this:

Macro expansion time may not be the time you want to force the user to
decide.  The only solution that suggests itself to me is to parametrize
the relevant functions on `stream', and specify the stream at the
top-level entry points.


Or Lisp global variables may be enough for your purpose.

(defvar *my-output-stream* *standard-output*)

And you could use that as the target stream for your formats (if not
otherwise parametrized).  At top level you could change this binding to
an open file, or a bidirectional stream or whatever.


Also Note that lisp permits you to do

  (with-open-file (*standard-output* "outputfile" :direcion :output)
       ...FORMS...)

Then all the output that would have been printed to standard output by
FORMS goes to `outputfile'.  If you go this route, you have to be
careful about the distinction that some lisps make betweeen
*standard-output* and *terminal-io*. IIRC write functions use the former
but (format T ) writes to the latter.

--
Madhu
From: Joost Kremers
Subject: Re: output either to file or stdout
Date: 
Message-ID: <slrngd6v13.30u.joostkremers@j.kremers4.news.arnhem.chello.nl>
Madhu wrote:
> * Joost Kremers <···························@j.kremers4.news.arnhem.chello.nl> :
> Wrote on 17 Sep 2008 15:09:11 GMT:
>
>| when i want to write output to a file, i can just use WITH-OPEN-FILE. if i
>| want to write output to the screen, i use format with T as the DESTINATION
>| argument.
>|
>| but what if i want to give the user the option of specifying whether he
>| wants the output to go to the screen or to a file?
>|
>| at first, i thought i could write the following macro do solve this:
>
> Macro expansion time may not be the time you want to force the user to
> decide.

exactly.

>  The only solution that suggests itself to me is to parametrize
> the relevant functions on `stream', and specify the stream at the
> top-level entry points.

yeah, but that's exactly what i do not want. i want the function to figure
out for itself whether it needs to create a file stream or whether it can
use *standard-output*. that is, if i call the function as

(my-func)

it should output to *standard-output*, if i call it as

(my-func :log "my-func.log")

it should send its output to the file my-func.log.

[...]
> Also Note that lisp permits you to do
>
>   (with-open-file (*standard-output* "outputfile" :direcion :output)
>        ...FORMS...)
>
> Then all the output that would have been printed to standard output by
> FORMS goes to `outputfile'.  If you go this route, you have to be
> careful about the distinction that some lisps make betweeen
> *standard-output* and *terminal-io*. IIRC write functions use the former
> but (format T ) writes to the latter.

thanks for the pointers. gives me a better idea of the options i have. 


-- 
Joost Kremers                                      ············@yahoo.com
Selbst in die Unterwelt dringt durch Spalten Licht
EN:SiS(9)
From: Rainer Joswig
Subject: Re: output either to file or stdout
Date: 
Message-ID: <joswig-957648.13350219092008@news-europe.giganews.com>
In article 
<···························@j.kremers4.news.arnhem.chello.nl>,
 Joost Kremers <············@yahoo.com> wrote:

> Madhu wrote:
> > * Joost Kremers <···························@j.kremers4.news.arnhem.chello.nl> :
> > Wrote on 17 Sep 2008 15:09:11 GMT:
> >
> >| when i want to write output to a file, i can just use WITH-OPEN-FILE. if i
> >| want to write output to the screen, i use format with T as the DESTINATION
> >| argument.
> >|
> >| but what if i want to give the user the option of specifying whether he
> >| wants the output to go to the screen or to a file?
> >|
> >| at first, i thought i could write the following macro do solve this:
> >
> > Macro expansion time may not be the time you want to force the user to
> > decide.
> 
> exactly.
> 
> >  The only solution that suggests itself to me is to parametrize
> > the relevant functions on `stream', and specify the stream at the
> > top-level entry points.
> 
> yeah, but that's exactly what i do not want. i want the function to figure
> out for itself whether it needs to create a file stream or whether it can
> use *standard-output*. that is, if i call the function as
> 
> (my-func)
> 
> it should output to *standard-output*, if i call it as
> 
> (my-func :log "my-func.log")
> 
> it should send its output to the file my-func.log.
> 
> [...]
> > Also Note that lisp permits you to do
> >
> >   (with-open-file (*standard-output* "outputfile" :direcion :output)
> >        ...FORMS...)
> >
> > Then all the output that would have been printed to standard output by
> > FORMS goes to `outputfile'.  If you go this route, you have to be
> > careful about the distinction that some lisps make betweeen
> > *standard-output* and *terminal-io*. IIRC write functions use the former
> > but (format T ) writes to the latter.
> 
> thanks for the pointers. gives me a better idea of the options i have.

Below function allows you to specify either nothing, a stream or a log-file.


(defun my-func (&key (log nil log-provided) (stream *standard-output*))
  (flet ((my-func-internal (stream)
           ; ...
           (does-a-lot-of-printing-stream)
           ; ...
           ))
    (if log-provided
        (with-open-file (stream log :direction :output)
          (my-func-internal stream))
      (my-func-internal stream))))

-- 
http://lispm.dyndns.org/
From: Thomas A. Russ
Subject: Re: output either to file or stdout
Date: 
Message-ID: <ymizlm4dtkc.fsf@blackcat.isi.edu>
Joost Kremers <············@yahoo.com> writes:
> changing the macro to the following works:
> 
> (defmacro with-output-destination ((var dest &rest rest) &body body)
>   `(if (eq ,dest t)
>        (let ((,var t))
> 	 ,@body)
>        (with-open-file (,var ,dest ,@rest)
> 	 ,@body)))
> 
> but i find this quite unpretty, given that now the macro body appears twice
> in the expansion.

Actually, I find that being able to have the body appear in more than
one place is a nice bonus of macros.  It allows one to be able to do
precisely the sort of contexual manipulation that your macro does, while
still maintaining a single "body" in the source code -- so you only have
to modify one place in the programmer-visible code, even if that then
appears in multiple places when run through the compiler.

If find that in other languages, when I have something similar, I end up
having to introduce auxiliary functions in order to achieve the same
effect, even when the functions then end up being relatively trivial.
This comes up for me every so often in Java, when dealing with the event
dispatch thread.  I would love to be able to have a nice macro system to
allow me to extend the syntax to allow something comparable to:

(defmacro ensure-event-dispatch-thread (&body body)
  `(if (event-dispatch-thread-p)
       (progn ,@body)
       (execute-on-event-dispatch-thread
         (lambda () ,@body))))

-- 
Thomas A. Russ,  USC/Information Sciences Institute
From: Joost Kremers
Subject: Re: output either to file or stdout
Date: 
Message-ID: <slrngdd7nb.3aa.joostkremers@j.kremers4.news.arnhem.chello.nl>
Thomas A. Russ wrote:
> Joost Kremers <············@yahoo.com> writes:
>> changing the macro to the following works:
>> 
>> (defmacro with-output-destination ((var dest &rest rest) &body body)
>>   `(if (eq ,dest t)
>>        (let ((,var t))
>> 	 ,@body)
>>        (with-open-file (,var ,dest ,@rest)
>> 	 ,@body)))
>> 
>> but i find this quite unpretty, given that now the macro body appears twice
>> in the expansion.
>
> Actually, I find that being able to have the body appear in more than
> one place is a nice bonus of macros.  It allows one to be able to do
> precisely the sort of contexual manipulation that your macro does, while
> still maintaining a single "body" in the source code -- so you only have
> to modify one place in the programmer-visible code, even if that then
> appears in multiple places when run through the compiler.

i think i'm slowly beginning to come round to your position. ;-) it's not
as if the code in the body is all that long... a couple of format
statements, that's it. there's not that much harm in duplicating that.


-- 
Joost Kremers                                      ············@yahoo.com
Selbst in die Unterwelt dringt durch Spalten Licht
EN:SiS(9)