[m-rev.] for review: automatically generate all type_ctor_infos on non C backends

Fergus Henderson fjh at cs.mu.OZ.AU
Thu Nov 20 13:40:44 AEDT 2003


On 19-Nov-2003, Peter Ross <pro at missioncriticalit.com> wrote:
> Get the compiler to generate the type_ctor_infos for all the types
> which were previously defined by hand.
> 
> This change completes the removal of all the MC++ from the compiler.

Good stuff, Pete.  Thanks!

> Index: compiler/make_hlds.m
> @@ -3743,8 +3743,9 @@
>  		)
>  	).
>  
> -add_special_pred_for_real(SpecialPredId, TVarSet, Type, TypeCtor,
> +add_special_pred_for_real(SpecialPredId, TVarSet, Type0, TypeCtor,
>  		TypeBody, Context, Status0, !Module) :-
> +	Type = remove_qualifiers_from_the_builtin_types(Type0),

This is OK for now, but in the long run, it would be nicer if the builtin
qualifiers were kept at all points.  (The reason that they are not is
purely historical.)  So please put an XXX comment about that here.

> Index: compiler/special_pred.m
...
>  can_generate_special_pred_clauses_for_type(ModuleInfo, TypeCtor, Body) :-
> -	Body \= abstract_type(_),
> +	(
> +		Body \= abstract_type(_)
> +	;
> +		% Only the types which have a unification defined in
> +		% private_builtin.m
> +		compiler_generated_rtti_for_the_builtins(ModuleInfo),
> +		Builtin = mercury_public_builtin_module,
> +		( TypeCtor = qualified(Builtin, "int") - 0
> +		; TypeCtor = qualified(Builtin, "string") - 0
> +		; TypeCtor = qualified(Builtin, "character") - 0
> +		; TypeCtor = qualified(Builtin, "float") - 0
> +		; TypeCtor = qualified(Builtin, "pred") - 0
> +		)

XXX There should be a comment in private_builtin.m pointing to this code.
XXX Treating pred/0 specially here seems odd and possibly wrong;
    what about func/0, pred/1, etc.?

> +	% The compiler generates the rtti for the builtins when we are
> +	% on the non C backends.
> +compiler_generated_rtti_for_the_builtins(ModuleInfo) :-
> +	module_info_globals(ModuleInfo, Globals),
> +	globals__get_target(Globals, Target),
> +	( Target = il ; Target = java ).

Please add a comment explaining the rationale for why we don't do this
for the C back-end.

> +++ compiler/type_ctor_info.m	19 Nov 2003 14:12:28 -0000
> @@ -111,10 +111,23 @@
>  			TypeModuleName = ModuleName,
>  			map__lookup(TypeTable, TypeCtor, TypeDefn),
>  			hlds_data__get_type_defn_body(TypeDefn, TypeBody),
> -			TypeBody \= abstract_type(_),
> -			\+ type_ctor_has_hand_defined_rtti(TypeCtor, TypeBody),
> -			( are_equivalence_types_expanded(ModuleInfo)
> -				=> TypeBody \= eqv_type(_) )
> +			(
> +				TypeBody \= abstract_type(_),
> +				\+ type_ctor_has_hand_defined_rtti(TypeCtor,
> +					TypeBody),
> +				( are_equivalence_types_expanded(ModuleInfo)
> +					=> TypeBody \= eqv_type(_) )
> +			;
> +				compiler_generated_rtti_for_the_builtins(
> +					ModuleInfo),
> +				TypeModuleName = unqualified(ModuleNameString),
> +				( builtin_type_ctor(ModuleNameString,
> +					TypeName, TypeArity, _)
> +				; impl_type_ctor(ModuleNameString,
> +					TypeName, TypeArity, _),
> +				  TypeName \= "ticket"
> +				)
> +			)

Please add some comments here.

I don't understand why we need to special-case "ticket";
that looks like it might be a bug.

> Index: compiler/type_util.m
...
> +	% Remove any of the qualifiers from the builtin types.
> +:- func remove_qualifiers_from_the_builtin_types(type) = (type).

I suggest s/the qualifiers/the "builtin." qualifiers/

The implementation doesn't match the documentation, since
the implementation only removes qualifiers from some of the builtin
types, not all of them.  E.g. it doesn't remove qualifiers from
"builtin.c_pointer".

> +	).
> +
> +remove_qualifiers_from_the_builtin_types(Type) = NormalizedType :-
> +	( type_to_ctor_and_args(Type, TypeCtor, []) ->
> +		Builtin = mercury_public_builtin_module,
> +		(
> +			TypeCtor = qualified(Builtin, Name) - 0,
> +			( Name = "int"
> +			; Name = "string"
> +			; Name = "character"
> +			; Name = "float"
> +			; Name = "pred"

That seems to be the second occurrence of that list of names
(the other was in special_pred.m, above).
It should be abstracted out into a separate predicate.

> Index: compiler/unify_proc.m
> +generate_builtin_unify(TypeCategory, H1, H2, Context, Clauses, !Info) :-
> +	ArgVars = [H1, H2],
> +	( TypeCategory = int_type,
> +		Name = "builtin_unify_int"
> +	; TypeCategory = char_type,
> +		Name = "builtin_unify_character"
> +	; TypeCategory = str_type,
> +		Name = "builtin_unify_string"
> +	; TypeCategory = float_type,
> +		Name = "builtin_unify_float"
> +	; TypeCategory = higher_order_type,
> +		Name = "builtin_unify_pred"
> +	; TypeCategory = tuple_type,
> +		error("generate_builtin_unify: tuple")
> +	; TypeCategory = enum_type,
> +		error("generate_builtin_unify: enum")
> +	; TypeCategory = variable_type,
> +		error("generate_builtin_unify: variable type")
> +	; TypeCategory = type_info_type,
> +		error("generate_builtin_unify: type_info type")
> +	; TypeCategory = type_ctor_info_type,
> +		error("generate_builtin_unify: type_ctor_info type")
> +	; TypeCategory = typeclass_info_type,
> +		error("generate_builtin_unify: typeclass_info type")
> +	; TypeCategory = base_typeclass_info_type,
> +		error("generate_builtin_unify: base_typeclass_info type")
> +	; TypeCategory = void_type,
> +		error("generate_builtin_unify: void type")
> +	; TypeCategory = user_ctor_type,
> +		error("generate_builtin_unify: user_ctor type")
> +	),

s/error/unexpected(this_file, /g

There should be a comment explaining why those cases should never occur
(or, if they can occur, the code should do something other than just
throwing an exception).

> +generate_builtin_compare(TypeCategory, Res, H1, H2, Context, Clauses, !Info) :-
> +	ArgVars = [Res, H1, H2],
> +	( TypeCategory = int_type,
> +		Name = "builtin_compare_int"
> +	; TypeCategory = char_type,
> +		Name = "builtin_compare_character"
> +	; TypeCategory = str_type,
> +		Name = "builtin_compare_string"
> +	; TypeCategory = float_type,
> +		Name = "builtin_compare_float"
> +	; TypeCategory = higher_order_type,
> +		Name = "builtin_compare_pred"
> +	; TypeCategory = tuple_type,
> +		Name = "builtin_compare_tuple",
> +		error("generate_builtin_compare: tuple type")
> +	; TypeCategory = enum_type,
> +		error("generate_builtin_compare: enum type")
> +	; TypeCategory = variable_type,
> +		error("generate_builtin_compare: variable type")
> +	; TypeCategory = type_info_type,
> +		error("generate_builtin_compare: type_info type")
> +	; TypeCategory = type_ctor_info_type,
> +		error("generate_builtin_compare: type_ctor_info type")
> +	; TypeCategory = typeclass_info_type,
> +		error("generate_builtin_compare: typeclass_info type")
> +	; TypeCategory = base_typeclass_info_type,
> +		error("generate_builtin_compare: base_typeclass_info type")
> +	; TypeCategory = void_type,
> +		error("generate_builtin_compare: void type")
> +	; TypeCategory = user_ctor_type,
> +		error("generate_builtin_compare: user_ctor type")
> +	),
> +	unify_proc__build_call(Name, ArgVars, Context, CompareGoal, !Info),
> +	quantify_clauses_body(ArgVars, CompareGoal, Context, Clauses, !Info).

Likewise.

> Index: library/Mmakefile
> @@ -287,14 +287,12 @@
>  CSHARP_MODULES = array builtin char construct dir exception float int io \
>  		library math private_builtin rtti_implementation std_util \
>  		string time type_desc
> -CPP_MODULES = builtin private_builtin type_desc
> +CPP_MODULES = 
>  
>  CSHARP_DLLS = $(CSHARP_MODULES:%=%__csharp_code.dll)
>  CPP_DLLS = $(CPP_MODULES:%=%__cpp_code.dll)
>  
>  $(CSHARP_DLLS) $(CPP_DLLS) : $(RUNTIME_DLLS)
> -
> -builtin__cpp_code.dll : builtin__csharp_code.dll

Since the intent is that we should never use MC++ again,
it's probably better to just delete all references to the CPP_*
variables.

> Index: library/builtin.m
...
> +++ library/builtin.m	19 Nov 2003 14:12:30 -0000
> +% These abstract type delcarations are needed so that the type_ctor
> +% is generated for these types.

s/delcarations/declarations/

Also I suggest you say "so that the compiler will generate the
type_ctors ..." instead of "so that the type_ctor is generated".

> +:- type int.
> +:- type string.
> +:- type character.
> +:- type float.
> +:- type (pred).
> +:- type (func).
> +:- type void.
> +:- type tuple.

What happened to c_pointer?

Also, I didn't see any similar declarations for the types
defined in other modules, e.g. in library/type_desc.m.

> Index: library/type_desc.m
...
>  public static int MR_compare_type_info(object[] t1, object[] t2) {
>  	object[] res = null;
>  
>  	mercury.type_desc.mercury_code.ML_call_rtti_compare_type_infos(
>  		ref res, t1, t2);
> -/*
> -#ifdef MR_HIGHLEVEL_DATA
> -	return res-> data_tag;
> -#else
> -*/
> +// currently comparison_results are always builting using low-level data.

"builting"?

> library/builtin.m:
> -		""called compare/3 for tuple type"");
> +		""called compare/3 for `tuple' type"");

The original phrasing was correct.  "tuple" should not be quoted here.

> Index: library/private_builtin.m
> @@ -389,41 +379,8 @@
>  
>  ").
>  
> -:- pragma foreign_code("MC++", "
> -
> -MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, type_ctor_info, 1,
> -	MR_TYPECTOR_REP_TYPECTORINFO) 
> -MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, type_info, 1,
> -	MR_TYPECTOR_REP_TYPEINFO) 
> -MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, base_typeclass_info, 1,
> -	MR_TYPECTOR_REP_BASETYPECLASSINFO) 
> -MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, typeclass_info, 1,
> -	MR_TYPECTOR_REP_TYPECLASSINFO) 
> -").
>  :- pragma foreign_code("C#", "
>  
> -/* XXX these macros need to be defined in C#
> -// MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, type_ctor_info, 1,
> -//	MR_TYPECTOR_REP_TYPECTORINFO) 
> -public static object[] __type_ctor_info_type_ctor_info_1;
> -public static object[] private_builtin__type_ctor_info_type_ctor_info_1;
> -
> -// MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, type_info, 1,
> -//	MR_TYPECTOR_REP_TYPEINFO) 
> -public static object[] __type_ctor_info_type_info_1;
> -public static object[] private_builtin__type_ctor_info_type_info_1;
> -
> -// MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, base_typeclass_info, 1,
> -//	MR_TYPECTOR_REP_BASETYPECLASSINFO) 
> -public static object[] __type_ctor_info_base_typeclass_info_1;
> -public static object[] private_builtin__type_ctor_info_base_typeclass_info_1;
> -
> -// MR_DEFINE_BUILTIN_TYPE_CTOR_INFO(private_builtin, typeclass_info, 1,
> -//	MR_TYPECTOR_REP_TYPECLASSINFO) 
> -public static object[] __type_ctor_info_typeclass_info_1;
> -public static object[] private_builtin__type_ctor_info_typeclass_info_1;
> -*/

Those seem to have been defined both with and without the
"private_builtin__prefixes"; are you sure that isn't still needed?

What sort of testing of the il grade have you done to ensure that
this change didn't break things?

There was quite a bit of code which was removed,
and some of it seems to have been _incorrectly_ removed.
For example, the code which reports the error
"incomparable floats in compare/3" was deleted,
and not replaced with anything.  That has to be a bug.

The chance of accidentally breaking things without any problem
showing up immediately is higher because of the fact that undefined
symbol errors may not be reported at link time, only at run time.
So some care will be required to ensure this doesn't happen.
[Running PEVerify might help detect such problems... although currently
I think that PEVerify issues a lot of spurious warnings, because
(a) in some places the code is unverifiable, and (b) spurious flow-on
errors following on from (a), caused by a bug in PEVerify.]

All of the code deletions quoted below are IMHO suspect.

Otherwise, this change looks fine.

> Index: library/builtin.m
...
>  public static bool
> -__Unify____int_0_0(int x, int y)
> -{
> -	return x == y;
> -}
> -
> -public static bool
> -__Unify____string_0_0(string x, string y)
> -{
> -	return System.String.Equals(x, y);
> -}
> -
> -public static bool
> -__Unify____character_0_0(char x, char y)
> -{
> -	return x == y;
> -}
> -
> -public static bool
> -__Unify____float_0_0(double x, double y)
> -{
> -	return x == y;
> -}
> -

> @@ -655,15 +588,7 @@
>  }
>  
>  public static bool
> -__Unify____pred_0_0(object[] x, object[] y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called unify for `pred' type"");
> -	return false;
> -}
> -
> @@ -671,71 +596,7 @@
>  }
>  
>  public static void
> -__Compare____int_0_0(ref object[] result, int x, int y)
> -{
> -	int r;
> -	if (x > y) {
> -		r = 2;
> -	} else if (x == y) {
> -		r = 0;
> -	} else {
> -		r = 1;
> -	}
> -	result = mercury.runtime.LowLevelData.make_enum(r);
> -}
> -
> -public static void
> -__Compare____float_0_0(ref object[] result, double x, double y)
> -{
> -	int r;
> -	if (x > y) {
> -		r = 2;
> -	} else if (x == y) {
> -		r = 0;
> -	} else if (x < y) {
> -		r = 1;
> -	} else {
> -		mercury.runtime.Errors.fatal_error(
> -			""incomparable floats in compare/3"");
> -		r = -1;
> -	}
> -	result = mercury.runtime.LowLevelData.make_enum(r);
> -}
> -
> -
> -public static void
> -__Compare____string_0_0(ref object[] result, string x, string y)
> -{
> -	int r;
> -	int res = System.String.Compare(x, y);
> -	if (res > 0) {
> -		r = 2;
> -	} else if (res == 0) {
> -		r = 0;
> -	} else {
> -		r = 1;
> -	}
> -	result = mercury.runtime.LowLevelData.make_enum(r);
> -}
> -
> -public static void
> -__Compare____character_0_0(
> -	ref object[] result, char x, char y)
> -{
> -	int r;
> -	if (x > y) {
> -		r = 2;
> -	} else if (x == y) {
> -		r = 0;
> -	} else {
> -		r = 1;
> -	}
> -	result = mercury.runtime.LowLevelData.make_enum(r);
> -}
> -
> @@ -759,163 +620,11 @@
>  }
>  
>  public static void
> -__Compare____pred_0_0(ref object[] result,
> -	object[] x, object[] y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called compare/3 for `pred' type"");
> -}
> -
...
> -/*
> -** Unification procedures with the arguments boxed.
> -** These are just wrappers which call the unboxed version.
> -*/
> -
> -public static bool
> -do_unify__int_0_0(object x, object y)
> -{
> -	return __Unify____int_0_0(
> -		System.Convert.ToInt32(x), 
> -		System.Convert.ToInt32(y)); 
> -}
> -
> -public static bool
> -do_unify__string_0_0(object x, object y)
> -{
> -	return __Unify____string_0_0((string) x, (string) y);
> -}
> -
> -public static bool
> -do_unify__float_0_0(object x, object y)
> -{
> -	return __Unify____float_0_0(
> -		System.Convert.ToDouble(x), 
> -		System.Convert.ToDouble(y)); 
> -}
> -
> -public static bool
> -do_unify__character_0_0(object x, object y)
> -{
> -	return __Unify____character_0_0(
> -		System.Convert.ToChar(x), 
> -		System.Convert.ToChar(y)); 
> -}
> -
> -public static bool
> -do_unify__void_0_0(object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called unify for type `void'"");
> -	return false;
> -}
> -
> -public static bool
> -do_unify__c_pointer_0_0(object x, object y)
> -{
> -	return __Unify____c_pointer_0_0((object[]) x, (object[]) y); 
> -}
> -
> -public static bool
> -do_unify__func_0_0(object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called unify for `func' type"");
> -	return false;
> -}
> -
> -public static bool
> -do_unify__pred_0_0(object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called unify for `pred' type"");
> -	return false;
> -}
> -
> -public static bool
> -do_unify__tuple_0_0(object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called unify for `tuple' type"");
> -	return false;
> -}
> -
> -/*
> -** Comparison procedures with the arguments boxed.
> -** These are just wrappers which call the unboxed version.
> -*/
> -
> -public static void
> -do_compare__int_0_0(ref object[] result, object x, object y)
> -{
> -	__Compare____int_0_0(ref result,
> -		System.Convert.ToInt32(x), 
> -		System.Convert.ToInt32(y)); 
> -}
> -
> -public static void
> -do_compare__string_0_0(ref object[] result, object x, object y)
> -{
> -	__Compare____string_0_0(ref result, (string) x, (string) y);
> -}
> -
> -public static void
> -do_compare__float_0_0(ref object[] result, object x, object y)
> -{
> -	__Compare____float_0_0(ref result,
> -		System.Convert.ToDouble(x), 
> -		System.Convert.ToDouble(y)); 
> -}
> -
> -public static void
> -do_compare__character_0_0(
> -	ref object[] result, object x, object y)
> -{
> -	__Compare____character_0_0(ref result, 
> -		System.Convert.ToChar(x), 
> -		System.Convert.ToChar(y)); 
> -}
> -
> -public static void
> -do_compare__void_0_0(ref object[] result, object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called compare/3 for type `void'"");
> -}
> -
> -public static void
> -do_compare__c_pointer_0_0(
> -	ref object[] result, object x, object y)
> -{
> -	__Compare____c_pointer_0_0(ref result, (object[]) x, (object[]) y);
> -}
> -
> -public static void
> -do_compare__func_0_0(ref object[] result, object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called compare/3 for func type"");
> -}
> -
> -public static void
> -do_compare__pred_0_0(ref object[] result, object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called compare/3 for pred type"");
> -}
> -
> -public static void
> -do_compare__tuple_0_0(ref object[] result, object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(

> Index: library/private_builtin.m
> @@ -389,41 +379,8 @@
> -public static bool
> -do_unify__type_ctor_info_1_0(object[] type_info, object x, object y)
> -{
> -	return __Unify____type_ctor_info_1_0(type_info, 
> -		(object[]) x, (object[]) y);
> -}
> -
> -public static bool
> -do_unify__type_info_1_0(object[] type_info, object x, object y)
> -{
> -	return __Unify____type_info_1_0(type_info, (object[]) x, (object[]) y);
> -}
> -
> -public static bool
> -do_unify__typeclass_info_1_0(object[] type_info, object x, object y)
> -{
> -	return __Unify____typeclass_info_1_0(type_info, 
> -		(object[]) x, (object[]) y);
> -}
> -
> -public static bool
> -do_unify__base_typeclass_info_1_0(object[] type_info, object x, object y)
> -{
> -	return __Unify____base_typeclass_info_1_0(type_info,
> -		(object[]) x, (object[]) y);
> -}
> -
> -public static void
> -do_compare__type_ctor_info_1_0(
> -	object[] type_info, ref object[] result, object x, object y)
> -{
> -	__Compare____type_ctor_info_1_0(
> -		type_info, ref result, (object[]) x, (object[]) y);
> -}
> -
> -public static void
> -do_compare__type_info_1_0(
> -	object[] type_info, ref object[] result, object x, object y)
> -{
> -	__Compare____type_info_1_0(type_info, ref result,
> -		(object[]) x, (object[]) y);
> -}
> -
> -public static void
> -do_compare__typeclass_info_1_0(
> -	object[] type_info, ref object[] result, object x, object y)
> -{
> -	__Compare____typeclass_info_1_0(type_info, ref result,
> -		(object[]) x, (object[]) y);
> -}
> -
> -public static void
> -do_compare__base_typeclass_info_1_0(
> -	object[] type_info, ref object[] result, object x, object y)
> -{
> -	__Compare____base_typeclass_info_1_0(type_info, ref result,
> -		(object[]) x, (object[]) y);
> -}
> -
>  ").
>  
>  :- pragma foreign_proc("C",
> -
...
>  public static bool
> -do_unify__heap_pointer_0_0(object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called unify for type `private_builtin:heap_pointer'"");
> -	return false;
> -}
> -
> -public static void
> -do_compare__heap_pointer_0_0(
> -	ref object[] result, object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called compare/3 for type `private_builtin:heap_pointer'"");
> -}
...
> -public static bool
> -do_unify__ref_1_0(object[] type_info, object x, object y)
> -{
> -	return x == y;
> -}
> -
...
> -public static void
> -do_compare__ref_1_0(
> -	object[] type_info, ref object[] result, object x, object y)
> -{
> -	mercury.runtime.Errors.fatal_error(
> -		""called compare/3 for type `private_builtin.ref'"");
> -}

> Index: library/type_desc.m
...
>  public static void
> -__Compare____type_ctor_desc_0_0(
> -	ref object[] result, object[] x, object[] y)
> -{
> -	mercury.runtime.Errors.SORRY(
> -		""foreign code for comparing type_ctor_descs"");
> -}
> -
> -public static bool
> -__Unify____type_ctor_desc_0_0(object[] x, object[] y)
> -{
> -	mercury.runtime.Errors.SORRY(
> -		""foreign code for unifying type_ctor_descs"");
> -	return false;
> -}
> -
...
> -public static bool
> -do_unify__type_ctor_desc_0_0(object x, object y)
> -{
> -	return __Unify____type_ctor_desc_0_0((object[]) x, (object[]) y);
> -}
> -
> -public static void
> -do_compare__type_ctor_desc_0_0(ref object[] result, object x, object y)
> -{
> -	__Compare____type_ctor_desc_0_0(ref result, (object[]) x, (object[]) y);
> -}
> -
> -public static void
> -__Compare____type_desc_0_0(ref object[] result, object[] x, object[] y)
> -{
> -	mercury.type_desc.mercury_code.ML_call_rtti_compare_type_infos(
> -		ref result, x, y);
> -}
> -
> -public static bool
> -__Unify____type_desc_0_0(object[] x, object[] y)
> -{
> -	return (MR_compare_type_info(x, y) == 0);
> -}
> -
...
> -}
> -
> -public static bool
> -do_unify__type_desc_0_0(object x, object y)
> -{
> -	return __Unify____type_desc_0_0((object[]) x, (object[]) y);
> -}
> -
> -public static void
> -do_compare__type_desc_0_0(
> -	ref object[] result, object x, object y)
> -{
> -	__Compare____type_desc_0_0(ref result, (object[]) x, (object[]) y);
>  }

-- 
Fergus Henderson <fjh at cs.mu.oz.au>  |  "I have always known that the pursuit
The University of Melbourne         |  of excellence is a lethal habit"
WWW: <http://www.cs.mu.oz.au/~fjh>  |     -- the last words of T. S. Garp.
--------------------------------------------------------------------------
mercury-reviews mailing list
post:  mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe:   Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------



More information about the reviews mailing list