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!
Sebastian.