[m-dev.] for discussion: stream library v2
Peter Ross
petdr at miscrit.be
Thu Sep 28 21:37:47 AEDT 2000
On Thu, Sep 28, 2000 at 02:34:18AM +1100, Fergus Henderson wrote:
> On 27-Sep-2000, Peter Ross <petdr at miscrit.be> wrote:
> > :- type lowlevel(S) ---> lowlevel(S).
>
> Is `S' here a state, or a handle?
> (See below for what I mean by this.)
>
> For the high-level interface `S' is clearly a state type,
> not a handle type, but for the low-level interface I'm confused.
>
For the lowlevel interface it is meant to represent a handle on the
stream. You use the handle to do the IO.
> > :- typeclass lowlevel(S) where [
> > % Did an error occur processing the stream?
> > semipure pred lowlevel__is_error(S::ui, string::out) is semidet
> > ].
> >
> > :- typeclass lowlevel__input(S) <= lowlevel(S) where [
> > % Read one character from the stream described by S.
> > % Fail if we reach eof or some error condition.
> > impure pred lowlevel__read_char(S::ui, char::out) is semidet,
> >
> > % Have we reached the eof for S?
> > semipure pred lowlevel__is_eof(S::ui) is semidet
> > ].
> >
> > :- typeclass lowlevel__output(S) <= lowlevel(S) where [
> > % Read one character from the current stream.
> > impure pred lowlevel__write_char(S::ui, char::in) is semidet
> > ].
>
> Why do you use `ui' modes here?
>
So that the program is mode correct. If you make it mode in, then the
following code gets a mode error at the call to lowlevel__write_char.
low_write_char(Chr, lowlevel(Stream), FinalStream) :-
( impure lowlevel__write_char(Stream, Chr) ->
FinalStream = lowlevel(Stream)
;
( semipure lowlevel__is_error(Stream, Err0) ->
Err = Err0
;
Err = "stream__write_char failed but there is no error message"
),
FinalStream = lowlevel(Stream),
throw(stream_error(Err))
).
> One serious problem with all that is that `ui' modes are not supported yet.
>
> So, at least for the short term, I think you should change all of
> those so that instead of `S::ui' they have `S::di, S::uo',
> and make that argument pair last so that we can use DCG notation,
> and change them from semidet to det with an extra `bool::out'
> parameter.
>
Is this really necessary? This interface is only for use defining
streams in C, so the lack of support for ui modes isn't a real issue?
> If you do that, then I think a nice side-effect is that the interface
> will be pure (at least if `S' is a state type rather than a handle type),
> so you can also drop the "semipure" and "impure" annotations.
>
As far as I am concerned there is an unhealthy fascination with making
this interface pure. I can't see why it matters, it is meant to be
lowlevel, yucky and easy for interfacing with streams defined in another
language. I think Tysons email puts it much better then I do.
> Another alternative would be to just leave out the lowlevel interface
> for now, and thus require everyone to use the high-level interface.
>
That however is a pain in the arse when it comes to implementing streams
in C, which is why I chose to implement the lowlevel interface because
nearly all the initial streams we will want to define will sit on top of
a C library.
> > % The state of the world for one stream of type S.
> > :- type stream(S).
>
> The comment here is not clear; what do you mean
> "the state of the world for one stream"?
> Do you just mean "the state of a stream"?
>
Yes it is meant to represent the state of the stream.
> > :- type stream(S)
> > ---> stream(
> > S, % Handle on the stream
> > list(char) % Putback characters
> > ).
>
> The comment here is also confusing; is `S' a handle to the stream,
> or is `S' the stream state (excluding putback chars)?
>
> Normally "handle" is used to mean an abstract kind of pointer or
> reference. A "handle" would not need to be uniquely moded, in
> general, since you modify what the handle refers to, rather than the
> handle itself.
>
It is a bit of both. It is used in a state like manner by the high
level interface, but it will also be needed to be used as a handle when
closing streams and such like. See my message
http://www.cs.mu.oz.au/research/mercury/mailing-lists/mercury-users/mercury-users.0009/0003.html
for a reason why you need a non unique handle on a stream.
> If the idea is that stream(S) is a state but S is a handle,
> then you need an additional opaque field in the stream(S) type,
> e.g. one of type c_pointer. Otherwise consider the problems
> that could arise if e.g. some predicate operating on streams
> is tabled, or if some nifty compiler optimization makes the same kind
> of assumptions as tabling does. Reading a character from a handle
> doesn't change the handle, and won't change the putback characters
> if they were already empty, but it had better change the stream(S).
>
> > % Given a handle to a stream construct a unique stream object
> > % which can be used to do IO on the stream.
> > % This object initialises its state of the world from the
> > % io__state. XXX This isn't such a good idea for string streams,
> > % but is for other streams.
> > :- pred stream__init(S::in, stream(S)::uo, io__state::di, io__state::uo) is det.
>
> OK, here you seem to be assuming that `S' is a handle and `stream(S)'
> is a state type. But what happens if you call `stream__init' twice in
> a row on the same handle? Won't you get two handles to the same state?
> And then won't updates to the two handles occur in an unspecified order?
>
> If the intent is to treat this as if it were a form of concurrency,
> then it should be cc_multi, not det.
>
> Here's an example of what I'm talking about:
>
> :- pred nasty(Handle::in, io__state::di, io__state::uo) <= highlevel__output(S).
> nasty(Handle) -->
> stream__init(Handle, StreamA0),
> stream__init(Handle, StreamB0),
> { stream__write_string("foo\n", StreamA0, StreamA1),
> stream__write_string("bar\n", StreamB0, StreamB1) }.
>
> There's two side effects here: which side effect happens first?
> If you don't use --strict-sequential, the compiler could reorder
> the two calls to stream__write_string, or even interleave them.
> Note that we could also use parallel conjunction (&) inside the curlies there.
>
> Hmm, that raises another problem: what about the singleton variables
> `StreamA1' and `StreamB1' in this example? If these streams are I/O
> streams, whose updates have externally visible side effects, don't you
> need some way to tie those variables back into the final io__state
> returned from main? Otherwise the compiler could just optimize away
> all the stuff in curlies, since it is det and has no output variables
> except the singletons, whose scope is local to that goal.
>
You have raised a problem with this approach here, however I can't see
how you can avoid this problem and also avoid the problem with
exceptions raised in my previous email.
Pete
--------------------------------------------------------------------------
mercury-developers mailing list
Post messages to: mercury-developers at cs.mu.oz.au
Administrative Queries: owner-mercury-developers at cs.mu.oz.au
Subscriptions: mercury-developers-request at cs.mu.oz.au
--------------------------------------------------------------------------
More information about the developers
mailing list