[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

output stream API

This page is part of the web mail archives of SRFI 68 from before July 7th, 2015. The new archives for SRFI 68 contain all messages, not just those from before July 7th, 2015.

Hello Mike,

I am still absorbing the I/O SRFI; it is quite a complicated thing.
It turns out, I keep forgetting the way this SRFI organizes I/O land.
The problem is not really the fact that there are three layers,
but that there are so many operations which pass around data
in different conventions (store in vector with start, end; without;
allocate new vector, ...) and so many helper functions, each with
its own name.

Here are a few concrete points:

1. One specific thing that I just tripped over: The return values of output-bytes,
output-char etc. are unspecified. Did I miss something, or shouldn't they
rather return the new stream, as a functional interface would?

2. For what it's worth, the input-<type> ops return an object and the
rest stream. Now in many cases the first thing to do is determine
the length/size of the object to detect EOF.

The interface could return that right away:

Instead of this (using SRFI-71 notation):

(let ((string stream (input-string stream)))
  (let ((n (string-length string)))
    (if (positive? n)
        ...process string[0..n-1]...)))
you get this:

(let ((n string stream (input-string stream)))
  (if (positive? n)
      ...process string[0..n-1]...))

Alternatively, you could define all input-<type> to return always
a 'return code' argument, i.e. input-<type> returns
1. return code (exact integer for nr. of bytes read),
2. one or more values read from the stream, and
3. the residual stream.
This will enable a future extension to read other types of objects
(numbers, ...) in the same convention. Without the return code I
would be more in favor of returning #f at EOF for uniformity (breaking
with SML Basis Library which is for a stronly typed language).

3. Whenever there is a vector-like object (byte-vector, string) one can
combine the two operations with start index and/or count into a single
operation by using optional arguments. This reduces the number of ops
by about 10 to 15. You might end up with these:

(reader-read-bytes! reader bytes [ start count ])
(writer-write-bytes! writer bytes [ start count ])
(input-bytes input-stream [ count ]) ; includes input-bytes-n
(input-string input-stream [ count ]) ; includes input-string-n
(output-bytes output-stream bytes [ start count ]) ; includes output-bytes-n
(output-string output-stream string [ start count ]) ; includes output-string-n
(read-bytes [ [ count ] input-port ]) ; includes read-bytes-n
(read-string [ [ count ] input-port ]) ; includes read-string-n
(display-bytes bytes [ start count ] [ output-port ]) ; includes display-bytes-n
(display-string string [ start count ] [ output-port ]) ; includes display-string-n

For input/read-bytes/-string one could even consider passing a special
symbol for the count argument, e.g. 'until-eof, indicating that the rest of the
input should be read.

4. One source of confusion for me is still the read/write/display legacy
of Scheme that went into this SRFI. I am aware that this is a touchy issue,
and breaking with tradition on this point might hurt more than it helps.
But are there other solutions?

5. There are no constructors for creating ports from streams. Is that intentional?
My understanding of a port is that it is conceptually a reference-cell for a stream,
although it might be implemented more efficiently.

6. It would be great if there were a mechanism specified for passing
additional arguments (options) between the levels. Learning from other
existing I/O libraries, it is a recurring problem that you need to pass
funny little hints (e.g. access permission flags) down (and sometimes
up) the protocol stack to do what you need to do. I am not talking about
arcane IOCTLs, but over simple things like opening a file for writing with
the right attributes to actually be able to write to it (this is no joke, R5RS
open-output-file does not specify what happens if the file exists.)

Unfortunately, this SRFI does not specify any mechnism for passing
optional arguments around, whatsoever.

I am very much afraid that this will lead to another variation of the
situation we have with the existing Scheme I/O: Everybody implements
the standard, everybody extends it to be practically useful on the host OS,
and yet there is no portable way to open a stupid plain-vanilla output file
because some systems need another procedure for that, some want a
symbol as optional argument etc. In the end, I write my own "standard"
abstract layer.

To be constructive and concrete, it would be sufficient for now to specify
that all open-<something> procedures would accept an optional argument,
e.g. called 'options', that they store in the object and pass to each other
when they call each other. This specification could be tightened such
that 'options' is an association list using EQV?. It's then up to the implementation
to define something sensible with it, or not, but at least I can portably
store the options in a data structure.

So much for now, and btw., I think this SRFI is a very useful initiative!