[m-dev.] stream typeclasses (again)

Julien Fischer juliensf at cs.mu.OZ.AU
Tue Feb 28 18:03:30 AEDT 2006


Hi,

I've spent a bit more time playing around with the stream typeclass proposal.
Attached is the (or at least my) latest version plus a number of example
streams.  (Ian, I think you mentioned you'd done some more work on this as
well?)

Major changes from last time:

* The error type is now part of the stream typeclass, (it's functionally
  dependent on the stream type).

* The close method has been removed (for the moment at least)

Cheers,
Julien.
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%

% File: stream.m.
% Authors: maclarty, juliensf (+ bits from extras/streams by petdr)
% Stability: low

%-----------------------------------------------------------------------------%

:- module stream.
:- interface.

:- import_module string.

%-----------------------------------------------------------------------------%
%
% Stream errors
%

:- type stream.name == string.

:- type stream.result(T, E)
    --->    ok(T)
    ;       eof
    ;       error(E).

:- typeclass stream.error(Error) where
[
    % Convert a stream error into a human-readable format.
    % e.g. for use in error messages.
    %
    func error_message(Error) = string
].

%-----------------------------------------------------------------------------%
%
% Streams
%

    % A stream consists of a base, a state to update and an error type.
    %
:- typeclass stream.stream(Stream, State, Error)
    <= (stream.error(Error), (Stream -> State, Error)) where
[ 
        % A human readable name describing the stream.
        %
        pred name(Stream::in, stream.name::out, State::di, State::uo) is det
].

%-----------------------------------------------------------------------------%
%
% Input streams
%

:- typeclass stream.input(Stream, Unit, State, Error)
    <= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
    pred get(Stream::in, stream.result(Unit, Error)::out,
        State::di, State::uo) is det
].

%-----------------------------------------------------------------------------%
%
% Output streams
%

:- typeclass stream.output(Stream, Unit, State, Error)
    <= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
    pred put(Stream::in, Unit::in, State::di, State::uo) is det
].

%-----------------------------------------------------------------------------%
%
% Duplex streams
%

:- typeclass stream.duplex(Stream, Unit, State, Error)
    <= ( stream.input(Stream,  Unit, State, Error),
         stream.output(Stream, Unit, State, Error),
         (Stream -> State, Error)) where [].

%----------------------------------------------------------------------------%
%
% Putback streams
%

:- typeclass stream.putback(Stream, Unit, State, Error)
    <= (stream.input(Stream, Unit, State, Error),
        (Stream -> State, Error)) where
[
    pred unget(Stream::in, Unit::in, State::di, State::uo) is det
]. 

:- typeclass stream.unbounded_putback(Stream, Unit, State, Error)
    <= ( stream.putback(Stream, Unit, State, Error),
         (Stream -> State, Error)) where [].

%----------------------------------------------------------------------------%
%
% Buffered streams
%

% XXX What about streams where we can control the buffering?

:- typeclass stream.buffered(Stream, State, Error)
    <= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
     pred flush(Stream::in, State::di, State::uo) is det
].

%----------------------------------------------------------------------------%
%
% Seekable streams
%

:- type stream.whence
    --->    set
    ;       cur
    ;       end.

    % XXX call this random_access?
    %
% :- typeclass stream.seekable(Stream, State, Error)
%         <= stream(Stream, State, Error) where [
%     pred seek(Stream::in, stream.whence::in, int::in, State::di, State::uo)
%         is det
% ].

%----------------------------------------------------------------------------%
%
% Line oriented streams
%

:- typeclass stream.text(Stream, State, Error)
    <= (stream(Stream, State, Error), (Stream -> State, Error)) where
[
    pred get_line(Stream::in, int::out, State::di, State::uo) is det,
    pred set_line(Stream::in, int::in,  State::di, State::uo) is det
].

%-----------------------------------------------------------------------------%

% It would probably also be useful to have something like the following.

% :- typeclass stream.standard_reader(Stream, Unit, State) 
%         <= ( stream.input(Stream, Unit, State),
%              stream.buffered(Stream, State),
%              stream.text(Stream, State)) where [].
%     
% :- typeclass stream.standard_writer(Stream, Unit, State)
%         <= ( stream.output(Stream, Unit, State),
%              stream.putback(Stream, Unit, State),
%              stream.text(Stream, State)) where [].

%-----------------------------------------------------------------------------%
:- end_module stream.
%-----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%

% File: buffer.m.
% Author: juliensf.

% This file provides a string buffer type that can be used to implement
% string streams.

%-----------------------------------------------------------------------------%

:- module buffer.
:- interface.

:- import_module char.
:- import_module io.
:- import_module std_util.
:- import_module stream.
:- import_module string.

%-----------------------------------------------------------------------------%

:- type buffer.

:- type buffer.name == string.

:- type buffer.error.

:- pred buffer.new(buffer.name::in, string::in, buffer::out,
    io::di, io::uo) is det.

    % Retrieve the name associated with a string buffer.
    %
:- func buffer.name(buffer) = string.

    % Get the next char from the front of the buffer.
    %
:- pred buffer.get_char(buffer::in, maybe(char)::out, io::di, io::uo) is det.

    % Place a character on the front of the buffer.
    %
:- pred buffer.unget_char(buffer::in, char::in, io::di, io::uo) is det.

    % Add a char to the back of the buffer.
    %
:- pred buffer.put_char(buffer::in, char::in, io::di, io::uo) is det.

    % Add a string to the back of the buffer. 
    %
:- pred buffer.put_string(buffer::in, string::in, io::di, io::uo) is det.

%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%

:- instance stream.error(buffer.error).
:- instance stream.stream(buffer, io.state, buffer.error).
:- instance stream.input(buffer, char, io.state, buffer.error).
:- instance stream.output(buffer,  char,   io.state, buffer.error).
:- instance stream.output(buffer,  string, io.state, buffer.error).
:- instance stream.duplex(buffer,  char,   io.state, buffer.error).
:- instance stream.putback(buffer, char,   io.state, buffer.error).
:- instance stream.unbounded_putback(buffer, char, io.state, buffer.error). 

%-----------------------------------------------------------------------------%
%
% Debugging predicates
%

    % Dump the contents of the buffer to stdout.
    %
:- pragma obsolete(buffer.dump/3).
:- pred buffer.dump(buffer::in, io::di, io::uo) is det.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

:- import_module exception.
:- import_module store.
:- import_module require.

%-----------------------------------------------------------------------------%

:- type buffer
    --->    buffer(
                buffer_name     :: string,
                buffer_contents :: io_mutvar(string)
        ).

:- type buffer.error ---> buffer_error.

%-----------------------------------------------------------------------------%

buffer.new(Name, Contents, Buffer, !IO) :-
    store.new_mutvar(Contents, Mutvar, !IO),
    Buffer = buffer(Name, Mutvar).

buffer.name(Buffer) = Buffer ^ buffer_name.

buffer.get_char(Buffer, MaybeChar, !IO) :-
    store.get_mutvar(Buffer ^ buffer_contents, Contents, !IO),
    ( string.first_char(Contents, Char, Remainder) ->
        MaybeChar = yes(Char),
        store.set_mutvar(Buffer ^ buffer_contents, Remainder, !IO)
    ;
        MaybeChar = no
    ).

buffer.unget_char(Buffer, Char, !IO) :-
    store.get_mutvar(Buffer ^ buffer_contents, Contents0, !IO),
    Contents = string.char_to_string(Char) ++ Contents0,
    store.set_mutvar(Buffer ^ buffer_contents, Contents, !IO).

buffer.put_char(Buffer, Char, !IO) :-
    store.get_mutvar(Buffer ^ buffer_contents, Contents0, !IO),
    Contents = Contents0 ++ string.char_to_string(Char),
    store.set_mutvar(Buffer ^ buffer_contents, Contents, !IO).

buffer.put_string(Buffer, String, !IO) :-
    store.get_mutvar(Buffer ^ buffer_contents, Contents0, !IO),
    Contents = Contents0 ++ String,
    store.set_mutvar(Buffer ^ buffer_contents, Contents, !IO).

%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%

:- instance stream.error(buffer.error) where
[
    error_message(_) = throw(software_error("buffer.error"))
].

:- instance stream.stream(buffer, io.state, buffer.error) where
[
    name(Buffer, buffer.name(Buffer), !IO)
].

:- instance stream.input(buffer, char, io.state, buffer.error) where
[
    ( get(Buffer, Result, !IO) :-
        buffer.get_char(Buffer, MaybeChar, !IO),
        (
            MaybeChar = yes(Char),
            Result    = ok(Char)
        ;
            MaybeChar = no,
            Result    = eof
        )
    )
].

:- instance stream.output(buffer, char, io.state, buffer.error) where
[
    pred(put/4) is buffer.put_char
].

:- instance stream.output(buffer, string, io.state, buffer.error) where
[
    pred(put/4) is buffer.put_string
].

:- instance stream.duplex(buffer, char, io.state, buffer.error) where [].

:- instance stream.putback(buffer, char, io.state, buffer.error) where
[
    pred(unget/4) is buffer.unget_char
].

:- instance stream.unbounded_putback(buffer, char, io.state, buffer.error)
    where [].
        
%-----------------------------------------------------------------------------%
%
% Debugging predicates
%

buffer.dump(Buffer, !IO) :-
    store.get_mutvar(Buffer ^ buffer_contents, Contents, !IO),
    io.write_string(Contents, !IO).

%-----------------------------------------------------------------------------%
:- end_module buffer.
%-----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------r
% vim: ft=mercury ts=4 sw=4 et wm=0 tw=0
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%

% File: stream_io.m.
% Author: juliensf

% This module provides file streams.
% XXX The io module needs a bit of rewriting for this to work correctly.

%-----------------------------------------------------------------------------%

:- module stream_io.
:- interface.

:- import_module char.
:- import_module io.
:- import_module stream.

%----------------------------------------------------------------------------%
%
% I/O errors
%

:- instance stream.error(io.error).

%----------------------------------------------------------------------------%
%----------------------------------------------------------------------------%
% 
% Text streams

:- type line == string.

%----------------------------------------------------------------------------%
%
% Text input streams
%

% XXX Commented out to avoid overlapping instance with io.output_stream,
% since they are both equivalent to io.stream.

% :- instance stream.stream(io.input_stream, io.state, io.error).
% :- instance stream.input(io.input_stream, char, io.state, io.error).
% :- instance stream.input(io.input_stream, line, io.state, io.error).
% :- instance stream.text(io.input_stream, io.state, io.error).
% :- instance stream.putback(io.input_stream, char, io.state, io.error).

%----------------------------------------------------------------------------%
%
% Text output streams
%

:- instance stream.stream(io.output_stream, io.state, io.error).
:- instance stream.output(io.output_stream, char,   io.state, io.error).
:- instance stream.output(io.output_stream, float,  io.state, io.error).
:- instance stream.output(io.output_stream, int,    io.state, io.error).
:- instance stream.output(io.output_stream, string, io.state, io.error).
:- instance stream.text(io.output_stream, io.state, io.error).
:- instance stream.buffered(io.output_stream, io.state, io.error).

%----------------------------------------------------------------------------%
%----------------------------------------------------------------------------%

:- implementation.

%----------------------------------------------------------------------------%

:- instance stream.error(io.error) where
[
    func(stream.error_message/1) is io.error_message
].

%----------------------------------------------------------------------------%
%
% Text input streams
%

% :- instance stream(io.input_stream, io.state, io.error) where
% [
%     pred(name/4) is io.input_stream_name
% ].
% 
% :- instance stream.input(io.input_stream, char, io.state, io.error) where
% [
%     ( get(Stream, Result, !IO) :-
%         io.read_char(Stream, Result0, !IO),
%         (
%             Result0 = ok(Char),
%             Result  = ok(Char)
%         ;
%             Result0 = eof,
%             Result  = eof
%         ;  
%             Result0 = error(IOError),
%             Result  = error(IOError)
%         )
%     )
% ]. 
% 
% :- instance stream.input(io.input_stream, line, io.state, io.error) where
% [
%     ( get(Stream, Result, !IO) :-
%         io.read_line_as_string(Stream, Result0, !IO),
%         (
%             Result0 = ok(Line),
%             Result  = ok(Line)
%         ;
%             Result0 = eof,
%             Result  = eof
%         ;
%             Result0 = error(IOError),
%             Result  = error(IOError)
%         )
%     )
% ].
% 
% :- instance stream.text(io.input_stream, io.state, io.error) where
% [
%     pred(get_line/4) is io.get_line_number,
%     pred(set_line/4) is io.set_line_number
% ].
% 
% :- instance stream.putback(io.input_stream, char, io.state, io.error) where
% [
%     pred(unget/4) is io.putback_char
% ].
% 

%----------------------------------------------------------------------------%
%
% Text output streams
%

:- instance stream(io.output_stream, io.state, io.error) where
[
    pred(name/4) is io.output_stream_name
].

:- instance stream.output(io.output_stream, char, io.state, io.error) where
[
    ( put(Stream, Char, !IO) :-
        io.write_char(Stream, Char, !IO)
    )
].

:- instance stream.output(io.output_stream, float, io.state, io.error) where
[
    ( put(Stream, Float, !IO) :-
        io.write_float(Stream, Float, !IO)
    )
].

:- instance stream.output(io.output_stream, int, io.state, io.error) where
[
    ( put(Stream, Integer, !IO) :-
        io.write_int(Stream, Integer, !IO)
    )
].

:- instance stream.output(io.output_stream, string, io.state, io.error) where
[
    ( put(Stream, String, !IO) :-
        io.write_string(Stream, String, !IO)
    )
].

:- instance stream.buffered(io.output_stream, io.state, io.error) where 
[
    pred(flush/3) is io.flush_output
].

:- instance stream.text(io.output_stream, io.state, io.error) where
[
    pred(get_line/4) is io.get_output_line_number,
    pred(set_line/4) is io.set_output_line_number
].

%----------------------------------------------------------------------------%
:- end_module stream_io.
%----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%

% File: rot13_stream.m.
% Author: juliensf.

% An example of an encryption stream using the stream module.

%-----------------------------------------------------------------------------%

:- module rot13_stream.
:- interface.

:- import_module char.
:- import_module io.
:- import_module std_util.
:- import_module stream.
:- import_module string.

%-----------------------------------------------------------------------------%

:- type rot13_stream.

:- type rot13_stream.name == string.

:- type rot13_stream.error.

:- pred rot13_stream.new(rot13_stream.name::in, rot13_stream::out,
    io::di, io::uo) is det.

:- func rot13_stream.name(rot13_stream) = string.

    % Get the next encrypted character.  Returns `no' if the stream is empty.
    %
:- pred rot13_stream.get(rot13_stream::in, maybe(char)::out,
    io::di, io::uo) is det.
 
     % Encrypt a character.
     %
:- pred rot13_stream.put_char(rot13_stream::in, char::in,
    io::di, io::uo) is det.

    % Encrypt a string.
    %
:- pred rot13_stream.put_string(rot13_stream::in, string::in,
    io::di, io::uo) is det.

%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%

:- instance stream.error(rot13_stream.error).
:- instance stream.stream(rot13_stream, io, rot13_stream.error).
:- instance stream.input(rot13_stream, char, io, rot13_stream.error).
:- instance stream.output(rot13_stream,  char,   io, rot13_stream.error).
:- instance stream.output(rot13_stream,  string, io, rot13_stream.error).
:- instance stream.duplex(rot13_stream,  char,   io, rot13_stream.error).

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

:- import_module exception.
:- import_module int.
:- import_module list.
:- import_module queue.
:- import_module require.
:- import_module store.
:- import_module svqueue.

%-----------------------------------------------------------------------------%

:- type rot13_stream.error ---> rot13_stream_error.

    % This isn't going to win any prizes for efficiency ...
    %
:- type rot13_stream
    --->    rot13(
                rot13_name  :: string,
                rot13_queue :: io_mutvar(queue(char))
            ).

%-----------------------------------------------------------------------------%

rot13_stream.new(Name, Rot13Stream, !IO) :-
    store.new_mutvar(queue.init, Mutvar, !IO),
    Rot13Stream = rot13(Name, Mutvar). 

rot13_stream.name(Rot13Stream) = Rot13Stream ^ rot13_name.

rot13_stream.get(Rot13Stream, Result, !IO) :-
    store.get_mutvar(Rot13Stream ^ rot13_queue, Queue0, !IO),
    ( queue.get(Queue0, Char, Queue) ->
        Result  = yes(Char),
        store.set_mutvar(Rot13Stream ^ rot13_queue, Queue, !IO)
    ;
        Result = no
    ).

rot13_stream.put_char(Rot13Stream, Char, !IO) :-
    some [!Queue] (
        store.get_mutvar(Rot13Stream ^ rot13_queue, !:Queue, !IO),
        EncryptedChar = rot13(Char),
        svqueue.put(EncryptedChar, !Queue),
        store.set_mutvar(Rot13Stream ^ rot13_queue, !.Queue, !IO)
    ).         

rot13_stream.put_string(Rot13Stream, String, !IO) :-
    some [!Queue] (
        store.get_mutvar(Rot13Stream ^ rot13_queue, !:Queue, !IO),
        EncryptedChars = list.map(rot13, string.to_char_list(String)),
        svqueue.put_list(EncryptedChars, !Queue),
        store.set_mutvar(Rot13Stream ^ rot13_queue, !.Queue, !IO)
    ).  

%-----------------------------------------------------------------------------%
%
% Instances for the stream typeclass
%

:- instance stream.error(rot13_stream.error) where
[
    error_message(_) = throw(software_error("rot13_stream.error"))
].

:- instance stream.stream(rot13_stream, io, rot13_stream.error) where
[
    name(Rot13Stream, rot13_stream.name(Rot13Stream), !IO)
].

:- instance stream.input(rot13_stream, char, io, rot13_stream.error) where
[
    ( get(Stream, Result, !IO) :-
        rot13_stream.get(Stream, Result0, !IO),
        (
            Result0 = yes(Char),
            Result  = ok(Char)
        ;
            Result0 = no,
            Result  = eof
        )
    )
].
        
:- instance stream.output(rot13_stream, char, io, rot13_stream.error) where
[
    pred(put/4) is rot13_stream.put_char
].

:- instance stream.output(rot13_stream, string, io, rot13_stream.error) where
[
    pred(put/4) is rot13_stream.put_string
].

:- instance stream.duplex(rot13_stream, char, io, rot13_stream.error)
    where [].

%-----------------------------------------------------------------------------%
%
% Pinched from samples/rot13/rot13_concise.m
%

:- func rot13(char) = char.

rot13(Char) = rot_n(13, Char).

:- func rot_n(int, char) = char.

rot_n(N, Char) = RotChar :-
	char_to_string(Char, CharString),
	( sub_string_search(alphabet, CharString, Index) ->
		NewIndex = (Index + N) mod cycle + cycle * (Index // cycle),
		index_det(alphabet, NewIndex, RotChar)
    ;
		RotChar = Char
	).
    
    % The length of `alphabet' should be a multiple of `cycle'.
    %
:- func alphabet = string.

alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".

:- func cycle = int.

cycle = 26.

%-----------------------------------------------------------------------------%
:- end_module rot13_stream.
%-----------------------------------------------------------------------------%
-------------- next part --------------
%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------r
% Copyright (C) 2006 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%

% File: test_streams.m.
% Author: juliensf.

% A simple testcase for the stream typeclass.

%-----------------------------------------------------------------------------%

:- module test_streams.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

:- import_module char.
:- import_module stream.
:- import_module buffer.
:- import_module stream_io.
:- import_module string.
:- import_module rot13_stream.

%-----------------------------------------------------------------------------%

main(!IO) :-
    buffer.new("<<string_buffer>>", "Hello", StringBuffer, !IO),
    io.stdout_stream(Stdout, !IO),
    stream.put(StringBuffer, " World", !IO),
    stream.put(StringBuffer, '!', !IO),
    stream.put(StringBuffer, '\n', !IO),
    rot13_stream.new("<<rot13_stream>>", Rot13Stream, !IO),
    stream.put(Rot13Stream, "Hello World!\n", !IO),
    io.write_string("StringBuffer = \n", !IO), 
    write('a', StringBuffer, Stdout, !IO),
    io.write_string("Rot13Stream = \n", !IO), 
    write('a', Rot13Stream,  Stdout, !IO).

:- pred write(Unit::unused, Input::in, Output::in, io::di, io::uo) is det
    <= (
            stream.input(Input, Unit, io, InputError),
            stream.output(Output, Unit, io, OutputError)
    ). 

write(Unit, InputStream, OutputStream, !IO) :-
    get(InputStream, Result, !IO),
    (   
        Result = ok(Char : Unit),
        stream.put(OutputStream, Char, !IO),
        write(Unit, InputStream, OutputStream, !IO)
    ;
        Result = eof
    ;
        Result = error(_)      
    ). 

%-----------------------------------------------------------------------------%
:- end_module test_streams.
%-----------------------------------------------------------------------------%


More information about the developers mailing list