[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: textual output primitives
Re: textual output primitives
Mon, 06 Jun 2016 22:22:06 +0200
Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)
Andy Wingo <address@hidden> skribis:
> I have a simple question. Imagine you have to write the section of the
> Guile manual on textual I/O. What interfaces do you recommend people to
> use to output characters and strings to a port?
> OK that's the status. Anyway, the conundrum: two of the primitives
> above, put-char and put-string, are not like the others.
> put-char is the same as write-char, with arguments reversed and no
> optional args. Why the extra name?
> There is a put-string in (rnrs io ports), but while it's spiritually the
> same, it's wrapped in the `with-textual-output-conditions' thing that
> our higher-level R6RS ports code uses -- which is fine for the purposes
> of a library that has to shim Guile exceptions to standard exceptions,
> and maybe we should rework our standard exceptions one day, but it's a
> layer above, making it not a primitive for the purposes of (ice-9
> I repeat the original question: what should we recommend for outputting
> characters and strings to ports?
> Recall that for binary I/O, our recommendation is that users use the
> R6RS-like interfaces from (ice-9 binary-ports).
> For textual input we recommend read-char/peek-char and (ice-9 rdelim).
> For textual output... well there's `display', `write', `format',
> `write-char'... but only `write-char' is a primitive. `display' and
> `write' are both big generic things that operate on datums. `format' is
> a monster too.
> For textual output, we need two primitives: one to encode a character to
> bytes and send it to a port, and something to encode a string (and if
> possible, a substring) to bytes and send those bytes to a port.
> You could use the string function for characters, but that would mean
> allocating a string to write a character, which would be slow. You
> could use the character fuction for strings, but that would be both slow
> and wrong (in a way). It would be slow because even if the output port
> is unbuffered, we want to be able to write the string all in one go, or
> at least in big chunks. The semantic wrongness is that if multiple
> processes are writing to a terminal at the same time, as a user you want
> to see interleaved lines, not interleaved characters.
> The R5RS answer is `write-char' and `display'. If you want to display a
> substring, use `substring' then `display' that. Fine. That does cause
> an allocation for displaying substrings though, and `(write-char ch)' is
> actually `(display ch)' and not `(write ch)', so it's not very
> consistent. For my purposes though I need a primitive that (ice-9
> sports) could replace and `display' is not a primitive. You would use
> this primitive to implement `display', is what I'm saying. So we need
> something else.
> The R6RS answer is `put-char' and `put-string'. This is nice. It
> doesn't rhyme with `display' or `write' or `write-char' but it's
> consistent in itself, has consistent argument order, and is consistent
> with the binary API which is also already exported in (ice-9
> binary-ports) -- put-bytevector, put-u8, and so on. `display' and
> `write' are treated as legacy in some way and R6RS even puts them in
> another module.
> The R7RS answer is `write-char' and `write-string'. `write-string'
> doesn't exist in Guile yet but it could exist. Thing is, `(write-string
> "foo")' is not the same as `(write "foo")' -- rather, it is `(display
> "foo")'. I think R7RS made the wrong choice in deciding to add
> `write-string', preferring consistency with the past (`write-char') over
> internal, extensible consistency. I also am convinced by R6RS rationale
> section 20.8 in which they argue for putting the port argument first to
> the new `put-u8', `get-bytevector', `put-string' and so on procedures,
> as it is consistent and places the optional arguments at the end of
> e.g. `put-string' closer to their related object -- i.e. that in
> `(put-string port str 6 10)', the 6 and 10 refer to `str'.
> Sadly, R7RS also chose to go with a `start'/`end' convention for
> subsequences rather than R6RS's `start'/`count', so the equivalent with
> `write-string' is `(write-string str port 6 16)'. Either system is fine
> but R7RS chose deliberate incompatibility here. Taylan points out that
> it's particularly egregious in the case of `bytevector-copy!', where the
> R6RS and R7RS versions of this procedure have arguments of similar types
> but different interpretations.
> Whew, lot of background. OK. So again, my question: imagine you have
> to write the section of the Guile manual on textual I/O. Given all of
> this, what do you recommend people to use to write characters and
> strings to a port?
> There do not appear to be many good solutions.
> - Adding put-char and put-string to the default environment would be
> cool but put-char duplicates the existing write-char, and we wouldn't
> have the rest of the R6RS I/O system in the (guile) module
> - Adding write-string would be nasty as it is inconsistent with the
> port implementation internally and with (ice-9 binary-ports)
> - Adding (ice-9 textual-ports) and putting put-char/put-string there
> would be OK, but then it's off to the side in a way; but then also it
> is more amenable to change in the future...
> - Recommending R6RS as-is is a no-go because we don't want to force
> loading all those modules, and also the
> `with-textual-output-conditions' thing is inefficient
> - We could add put-string and put-char to (ice-9 binary-ports), but
> then that makes no sense
> - We could design some new I/O module hierarchy but it would be easy to
> make bad decisions
> I am leaning towards adding the bindings to (ice-9 binary-ports), and
> blessing binary-ports as just a random grab-bag of user-facing
> "primitive" operations on ports. I guess most people will keep using
> `display' for everything anyway, unless they are really caring about
> I/O, in which case they are probably using binary-ports in some way
I have a slight preference for the introduction of (ice-9
textual-ports), which seems consistent.
Basically, I agree what we want what R6 provides, minus the overhead, in
particular that of ‘with-textual-output-conditions’.
(Though I confess that in my non-R6 code I still use (rnrs io ports),
even though I should be using (ice-9 binary-ports).)
Thanks for the nice summary! :-)