subtyping proposal

Fergus Henderson fjh at cs.mu.OZ.AU
Tue Feb 10 03:56:15 AEDT 1998


Here's a suggestion for a language extension,
namely a new `:- subtype' declaration.

Syntax:

	:- subtype SubType < Type :: Inst.
	:- subtype SubType < Type ---> BoundInst.

Legality rules:

	`SubType' must be a non-variable term whose arguments (if any)
	are distinct variables.
	`Type' must be a type, or another subtype.
	`Inst' must be a ground inst.  
	`BoundInst' must be a disjunction of non-variable terms whose
	arguments are ground insts.
	`Type', `Inst' and `BoundInst' must not contain any variables
	that don't occur in `SubType'.
	If `Type' is another subtype, then `Inst' or `BoundInst' must
	specify a set of values which is a subset of the values allowed
	for `Type'.

Semantics:

	A subtype declaration

		:- subtype SubType < Type ---> BoundInst.

	is equivalent to the declaration

		:- type SubType < Type :: bound(BoundInst).

	A subtype declaration

		:- subtype SubType < Type :: Inst.

	is equivalent to the declaration

		:- type SubType == Type.
		:- inst SubInst :: Inst.

	where `SubInst' is textually the same as `SubType',
	with the following additional effect: for any argument
	whose type is declared as `SubType', the subtype information
	specified by the inst `SubInst' will be propagated into all the
	mode declarations for that argument. 
	[For example, if `SubInst' is `bound(Functors)', then any
	`ground' inst in the mode of an argument for that subtype
	will become `bound(Functors)', , any `unique' will become
	`unique(Functors)', etc., with the information being
	propagated into functor arguments recursively.]

Example:

	:- type fruit ---> apple ; orange ; lemon.
	:- subtype citrus < fruit ---> orange ; lemon.

	:- pred p(fruit::in, citrus::out).
	:- pred q(citrus::di, citrus::uo).

i.e.

	:- pred p(fruit::ground->ground, citrus::free->ground).
	:- pred q(citrus::unique->unique, citrus::free->unique).

This is equivalent to

	:- type fruit ---> apple ; orange ; lemon.
	:- type citrus == fruit.
	:- inst ground_citrus == bound(orange ; lemon).
	:- inst unique_citrus == unique(orange ; lemon).

	:- pred p(fruit::in, fruit::out(ground_citrus)).
	:- pred q(fruit::di(unique_citrus), fruit::out(unique_citrus)).

i.e.

	:- pred p(fruit::ground->ground, fruit::free->citrus).
	:- pred q(fruit::uniq_citrus->dead, fruit::free->uniq_citrus).

Rationale:
	
	This language extension makes it simpler for users to use
	subtypes, without needing to know the mode system in quite so
	much detail.  It makes it easier to convert a program that
	didn't use subtypes into one that does, and avoids putting
	unnecessary clutter in the mode declarations.

	This change would allow us to use subtypes to define the
	io__stream types

		io__stream
		io__text_stream
		io__input_stream
		io__output_stream
		io__bidirectional_stream
		io__binary_stream
		io__binary_input_stream
		io__binary_output_stream
		io__binary_bidirectional_stream

	with an appropriate subtype relation.
	The subtype relation here involves multiple inheritence:

					io__stream
				 	/         \
				       /	   \
			io__text_stream	  	   io__binary_stream
			/         \			/       \
	    io__input_stream   io__output_stream      ...	...
		        \         /			\       /
		   io__bidirectional_stream		 .......

	This could be handled as follows:

		:- type io__stream.
			% abstract, but conceptually defined
			% as follows:
			% :- type io__stream --->
			%		io__stream(
			%		text_or_binary,
			%		bool,	% input
			%		bool,	% output
			%		c_pointer).
			% :- type text_or_binary ---> text ; binary.
		
		:- inst ? == ground.
		:- inst yes ---> yes.
		:- inst text ---> text.
		:- inst binary ---> binary.

		:- subtype io__text_stream < io__stream
			---> io__stream(text, ?, ?, ?).

		:- subtype io__binary_stream < io__stream
			---> io__stream(binary, ?, ?, ?).

		:- subtype io__input_stream < io__text_stream
			---> io__stream(text, yes, ?, ?).

		:- subtype io__output_stream < io__text_stream
			---> io__stream(text, ?, yes, ?).

		:- subtype io__bidirectional_stream < io__text_stream
			---> io__stream(text, yes, yes, ?).

Drawbacks:
	
	One problem with this proposal as it stands is that using the
	mode system for subtypes doesn't work well with polymorphic
	predicates or containers.

	We could fix these problems by adding support for polymorphic
	modes, with the inst variables required to name ground insts.
	This restriction would make polymorphic modes relatively easy
	to implement.  For polymorphically typed predicates, we could
	propagate type information into the modes so that all polymorphically
	typed predicates also become polymorphically moded.

	For example, for `set__singleton_set(set(T)::out, T::in)',
	if at a particular call the 2nd argument has inst `bound(foo)',
	then on exit the first argument would have inst `set(bound(foo))'
	where `set(I)' is a (compiler-generated) polymorphic inst
	whose shape reflects the definition of the `set(T)' type.

	That is unsound in the case of programs that use std_util__construct/3
	(e.g. via io__read/3 or term__to_type/2).  However, we could patch
	up that hole by adding a typeclass `constructable(T)',
	declaring std_util_construct/3 and its callers to take arguments
	of that typeclass, and allowing the propagation of subtype
	information only for polymorphic type variables which are not
	constrained to be a subclass of `constructable'.
	
Counter-Rationale:

	We already have one mechanism for subtypes/inheritence,
	namely type classes; do we need another one?

-----------------------------------------------------------------------------

While I'm at it, here's an alternative syntax that might (or might not)
be better.

Syntax:

	:- subtype SubType < Type ---> Functors.

Legality Rules:

	`Functors' is a disjunction of non-variable terms
	whose arguments are types or subtypes.
	`Type' and `Functors' must not contain any variables
	that don't occur in `SubType', except that `Functors' may
	contain "don't care" (underscore) variables.
	If `Type' is another subtype, then `Functors' must specify a
	set of values which is a subset of the values allowed for `Type'.

Semantics:

	Any "don't care" (underscore) variables in `Functors' are
	replaced with `ground'.

	A subtype declaration

		:- subtype SubType < Type ---> Functors.

	is equivalent to the declaration

		:- type SubType == Type.

	with the following additional effect: for any argument
	whose type is declared as `SubType', the subtype information
	specified by `Functors' will be propagated into all the
	mode declarations for that argument.  Any `ground' will
	become `bound(Functors)', , any `unique' will become
	`unique(Functors)', etc., with the arguments of `Functors'
	having the type information recursively propagated into the
	mode in the same manner.

Example:

	The io__stream example shown above would look like this:

		:- subtype yes < bool ---> yes.
		:- subtype no < bool ---> no.
		:- subtype text < text_or_binary ---> text.
		:- subtype binary < text_or_binary ---> text.

		:- subtype io__binary_stream < io__stream
			---> io__stream(binary, _, _, _).

		:- subtype io__text_stream < io__stream
			---> io__stream(text, _, _, _).

		:- subtype io__input_stream < io__text_stream
			---> io__stream(_, yes, _, _).

		:- subtype io__output_stream < io__text_stream
			---> io__stream(_, _, yes, _).

		:- subtype io__bidirectional_stream < io__text_stream
			---> io__stream(_, _, yes, _).


Comments?


-- 
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