[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