subtyping proposal

Fergus Henderson fjh at cs.mu.OZ.AU
Tue Feb 10 17:52:38 AEDT 1998


On 10-Feb-1998, David Glen JEFFERY <dgj at cs.mu.OZ.AU> wrote:
> On 10-Feb-1998, Fergus Henderson <fjh at cs.mu.OZ.AU> wrote:
> > Here's a suggestion for a language extension,
> > namely a new `:- subtype' declaration.
> 
> Not a bad idea, but I have a few comments.
> 
> > 	We already have one mechanism for subtypes/inheritence,
> > 	namely type classes; do we need another one?
> 
> This is certainly true. Perhaps it would be better to hold off on this for a
> while until we have come to terms with programming with type classes a little
> more.
> 
> In particular, I think that the following example would be better solved using
> a type class approach:

You might be right.  I'm currently still undecided.

However, let me point out some disadvantages of the typeclass approach.
The user now needs to decide whether to write

	:- pred my_pred(io__input_stream, io__state, io__state).
	:- mode my_pred(in, di, di) is det.

or

	:- pred my_pred(InputStream, io__state, io__state)
			<= io__input_stream(InputStream).
	:- mode my_pred(in, di, di) is det.

If they use the former (monomorphic) declaration, then their predicate
won't work for arguments of types such as io__bidirectional_stream.

If they use the latter declaration, which is I presume what you
would advocate, then
	(a) the type declaration is more complex
	(b) hence the resulting type errors will be harder to understand
and
	(c) their program will probably be a little bit less efficient.

(a) and (b) together manage to alienate both the old Prolog hackers
and the newbies ;-)

For problem (c), I guess we can try throwing a lot of compiler
optimization at it, but solving it completely would require some
heroics (in addition to the usual cross-module specialization, 
we'd need to write our own linker which is capable of merging code
for different specializations that happen to be identical).

> Anyhow, that's just a rough proposal. Ideas?

One issue is that it would be nice to get both string formatting and
I/O using a single interface.  Also, ideally it should be possible to
using the string formatting stuff without having an io__state handy.

It is not possible to fulfil this requirement using subtypes.

You can do it with type classes, but this would require a more
complicated class interface using a multi-parameter type class
(which would increasing the effect of disadvantages (a) and (b)).

	% in io.m
	:- interface.
	:- typeclass io__input_stream(Stream, State) where [ 
		pred current_input_stream(Stream, State, State)
		mode current_input_stream(out, di, uo) is det

		pred read_char(Stream, io__result(char), State, State)
		mode read_char(in, out, di, uo) is det

		% ...
	].
	:- instance io__input_stream(io__input_stream, io__state).


	% in user program
	:- pred my_pred(InputStream, State, State)
			<= io__input_stream(InputStream, State).
	:- mode my_pred(in, di, di) is det.


	% string_stream.m
	:- module string_stream.m
	:- interface.

	% note: unlike io__state which supports multiple streams,
	% each string_stream_state has exactly one stream.

	:- type string_in_stream.
	:- type string_in_stream_state.

	:- type string_out_stream.
	:- type string_out_stream_state.

	:- instance io__input_stream(string_in_stream, string_in_stream_state).
	:- instance io__output_stream(string_out_stream,
					string_out_stream_state).

	:- func make_string_in_stream(string) = string_in_stream_state.
	:- mode make_string_in_stream(in) = uo is det.

	:- func make_string_out_stream = string_out_stream_state.
	:- mode make_string_out_stream = uo is det.

	:- func finish_string_out_stream(string_out_stream_state) = string.
	:- mode finish_string_out_stream(di) = out is det.

Perhaps with

	s/string_in_stream/istrstream/g
	s/string_out_stream/ostrstream/g

if you prefer short but slightly more cryptic names.

Note that you could do string streams using subtypes, rather than type
classes.  The interface is in fact significantly simpler.
The disadvantage is just that you need to use an io__state.

	:- module string_stream.m
	:- interface.

	:- subtype string_in_stream < io__input_stream
		:: string_in_stream_inst.

	:- subtype string_out_stream < io__output_stream
		:: string_out_stream_inst.

	:- pred make_string_in_stream(string, string_in_stream,
					io__state, io__state).
	:- mode make_string_in_stream(in, out, di, uo) is det.

	:- pred make_string_out_stream(string_out_stream, io__state, io__state).
	:- mode make_string_out_stream(out, di, uo) is det.

	:- pred finish_string_out_stream(string_out_stream, string,
					io__state, io__state).
	:- mode finish_string_out_stream(in, out, di, uo) is det.

If we're designing a language to be popular with the experts, I'd
definitely go for the typeclass solution.  Experts are willing
to cope with a significant increase in complexity if it gives
them a significant increase in expressiveness.  But if we're
designing a language for the masses, then I'm not sure which is
the best way to go.  Language experts would turn their noses
up at calls to unsafe_perform_io, but naive users might well
find that simpler to understand than getting the type quantifiers
right for some complicated type class hierarchy.

-- 
Fergus Henderson <fjh at cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh at 128.250.37.3         |     -- the last words of T. S. Garp.



More information about the developers mailing list