[m-rev.] for review: support different clauses for different modes

Fergus Henderson fjh at cs.mu.OZ.AU
Wed May 16 06:17:58 AEST 2001


Estimated hours taken: 8
Branches: main

Add support for using different clauses for different modes of a
predicate or function, using mode annotations on the clauses.

compiler/make_hlds.m:
	Add support for mode annotations on clauses.

compiler/module_qual.m:
	Add new routine qualify_clause_mode_list, for use by make_hlds.m.

compiler/mode_errors.m:
	Export output_mode_decl, for use by make_hlds.m

compiler/purity.m:
	Treat procedures with different clauses for different modes
	as impure, unless promised pure.

compiler/notes/compiler_design.html:
	Specify when module qualification of modes in
	clause mode annotations is done (in make_hlds.m).

NEWS:
doc/reference_manual.texi:
	Document the new feature.

Workspace: /home/mars/fjh/ws1/mercury
Index: NEWS
===================================================================
RCS file: /home/mercury1/repository/mercury/NEWS,v
retrieving revision 1.207
diff -u -d -r1.207 NEWS
--- NEWS	2001/05/02 17:34:26	1.207
+++ NEWS	2001/05/15 18:33:17
@@ -7,6 +7,21 @@
 of our CVS repository (the `version-0_10_y' branch).
 
 Changes to the Mercury language:
+* We've extended the language to allow you to specify different clauses
+  for different modes of a predicate or function.  This is done by
+  putting a determinism annotation in the head of each clause.
+  For example, you can write
+
+	:- mode p(in).
+	:- mode p(out).
+	p(X::in) :- ... /* clause for the `in' mode */
+	p(X::out) :- ... /* clause for the `out' mode */
+
+  For predicates or functions which have different clauses for different
+  modes, you need to either (1) add a `pragma promise_pure' declaration
+  for the predicate or function, and ensure that the declarative semantics
+  remains the same in each mode, or (2) declare the predicate as impure.
+
 * We now allow `:- pragma promise_semipure' declarations. For more
   information, see the "Impurity" chapter of the Mercury Language
   Reference Manual.
Index: compiler/make_hlds.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/make_hlds.m,v
retrieving revision 1.370
diff -u -d -r1.370 make_hlds.m
--- compiler/make_hlds.m	2001/05/02 17:34:35	1.370
+++ compiler/make_hlds.m	2001/05/15 16:11:07
@@ -94,7 +94,7 @@
 :- import_module prog_io, prog_io_goal, prog_io_dcg, prog_io_util, prog_out.
 :- import_module modules, module_qual, prog_util, options, hlds_out, typecheck.
 :- import_module make_tags, quantification, (inst), globals.
-:- import_module code_util, unify_proc, type_util, mode_util.
+:- import_module code_util, unify_proc, type_util, mode_util, mode_errors.
 :- import_module mercury_to_mercury, passes_aux, clause_to_proc, inst_match.
 :- import_module fact_table, purity, goal_util, term_util, export, llds.
 :- import_module error_util, foreign.
@@ -3716,12 +3716,14 @@
 		{ pred_info_clauses_info(PredInfo1, Clauses0) },
 		{ pred_info_typevarset(PredInfo1, TVarSet0) },
 		{ maybe_add_default_func_mode(PredInfo1, PredInfo2, _) },
-		{ pred_info_all_procids(PredInfo2, ProcIds) },
-		clauses_info_add_clause(Clauses0, ProcIds,
-			ClauseVarSet, TVarSet0, Args, Body, Context,
+		select_applicable_modes(Args, ClauseVarSet, Context,
+			PredId, PredInfo2, ModuleInfo1, Info0,
+			ArgTerms, ProcIdsForThisClause, ModuleInfo2, Info1),
+		clauses_info_add_clause(Clauses0, ProcIdsForThisClause,
+			ClauseVarSet, TVarSet0, ArgTerms, Body, Context,
 			Status, PredOrFunc, Arity, IsAssertion, Goal,
 			VarSet, TVarSet, Clauses, Warnings,
-			ModuleInfo1, ModuleInfo2, Info0, Info),
+			ModuleInfo2, ModuleInfo3, Info1, Info),
 		{
 		pred_info_set_clauses_info(PredInfo2, Clauses, PredInfo3),
 		(
@@ -3741,6 +3743,7 @@
 		% check if there are still no modes for the predicate,
 		% and if so, set the `infer_modes' flag for that predicate
 		%
+		pred_info_all_procids(PredInfo6, ProcIds),
 		( ProcIds = [] ->
 			pred_info_get_markers(PredInfo6, Markers0),
 			add_marker(Markers0, infer_modes, Markers),
@@ -3751,7 +3754,7 @@
 		map__det_update(Preds0, PredId, PredInfo, Preds),
 		predicate_table_set_preds(PredicateTable2, Preds,
 			PredicateTable),
-		module_info_set_predicate_table(ModuleInfo2, PredicateTable,
+		module_info_set_predicate_table(ModuleInfo3, PredicateTable,
 			ModuleInfo)
 		},
 		( { Status \= opt_imported } ->
@@ -3766,6 +3769,130 @@
 		)
 	).
 
+	% Extract the mode annotations (if any) from the clause arguments,
+	% and determine which mode(s) this clause should apply to.
+
+:- pred select_applicable_modes(list(prog_term)::in, prog_varset::in,
+		prog_context::in, pred_id::in, pred_info::in,
+		module_info::in, qual_info::in,
+		list(prog_term)::out, list(proc_id)::out,
+		module_info::out, qual_info::out,
+		io__state::di, io__state::uo) is det.
+
+select_applicable_modes(Args0, VarSet, Context, PredId, PredInfo, ModuleInfo0,
+		Info0, Args, ProcIds, ModuleInfo, Info) -->
+	{ get_mode_annotations(Args0, Args, empty, ModeAnnotations) },
+	(
+		{ ModeAnnotations = modes(ModeList0) },
+
+		%
+		% The user specified some mode annotations on this clause.
+		% First module-qualify the mode annotations.
+		%
+		{ qual_info_get_mq_info(Info0, MQInfo0) },
+		module_qual__qualify_clause_mode_list(ModeList0, ModeList,
+			Context, MQInfo0, MQInfo),
+		{ qual_info_set_mq_info(Info0, MQInfo, Info) },
+
+		%
+		% Now find the procedure which matches these mode annotations.
+		%
+		{ pred_info_procedures(PredInfo, Procs) },
+		{ map__to_assoc_list(Procs, ExistingProcs) },
+		(
+			{ get_procedure_matching_declmodes(ExistingProcs,
+				ModeList, ModuleInfo0, ProcId) }
+		->
+			{ ProcIds = [ProcId] },
+			{ ModuleInfo = ModuleInfo0 }
+		;
+			{ module_info_incr_errors(ModuleInfo0, ModuleInfo) },
+			undeclared_mode_error(
+				ModeList, VarSet, PredId, PredInfo,
+				ModuleInfo, Context),
+			% apply the clause to all modes
+			% XXX would it be better to apply it to none?
+			{ pred_info_all_procids(PredInfo, ProcIds) }
+		)
+	;
+		{ ModeAnnotations = empty },
+		{ pred_info_all_procids(PredInfo, ProcIds) },
+		{ ModuleInfo = ModuleInfo0 },
+		{ Info = Info0 }
+	;
+		{ ModeAnnotations = none },
+		{ pred_info_all_procids(PredInfo, ProcIds) },
+		{ ModuleInfo = ModuleInfo0 },
+		{ Info = Info0 }
+	;
+		{ ModeAnnotations = mixed },
+		{ module_info_incr_errors(ModuleInfo0, ModuleInfo) },
+		{ Info = Info0 },
+		io__set_exit_status(1),
+		prog_out__write_context(Context),
+		io__write_string("In clause for "),
+		hlds_out__write_pred_id(ModuleInfo, PredId),
+		io__write_string(":\n"),
+		prog_out__write_context(Context),
+		io__write_string(
+	"  syntax error: some but not all arguments have mode annotations.\n"),
+		% apply the clause to all modes
+		% XXX would it be better to apply it to none?
+		{ pred_info_all_procids(PredInfo, ProcIds) }
+	).
+			
+	% Clauses can have mode annotations on them, to indicate that the
+	% clause should only be used for particular modes of a predicate.
+	% This type specifies the mode annotations on a clause.
+:- type mode_annotations
+	--->	empty	% No arguments.
+	;	none	% One or more arguments,
+			% each without any mode annotations.
+	;	modes(list(mode))
+			% One or more arguments, each with a mode annotation.
+	;	mixed   % Two or more arguments, including some with mode
+			% annotations and some without.  (This is not allowed.)
+	.
+
+
+	% Extract the mode annotations (if any) from a list of arguments.
+:- pred get_mode_annotations(list(prog_term)::in, list(prog_term)::out,
+		mode_annotations::in, mode_annotations::out) is det.
+
+get_mode_annotations([], [], Annotations, Annotations).
+get_mode_annotations([Arg0 | Args0], [Arg | Args],
+		Annotations0, Annotations) :-
+	get_mode_annotation(Arg0, Arg, MaybeAnnotation),
+	add_annotation(Annotations0, MaybeAnnotation, Annotations1),
+	get_mode_annotations(Args0, Args, Annotations1, Annotations).
+
+:- pred add_annotation(mode_annotations::in, maybe(mode)::in,
+		mode_annotations::out) is det.
+
+add_annotation(empty, no, none).
+add_annotation(empty, yes(Mode), modes([Mode])).
+add_annotation(modes(_), no, mixed).
+add_annotation(modes(Modes), yes(Mode), modes(Modes ++ [Mode])).
+add_annotation(none, no, none).
+add_annotation(none, yes(_), mixed).
+add_annotation(mixed, _, mixed).
+
+	% Extract the mode annotations (if any) from a single argument.
+:- pred get_mode_annotation(prog_term::in, prog_term::out, maybe(mode)::out)
+		is det.
+
+get_mode_annotation(Arg0, Arg, MaybeAnnotation) :-
+	(
+		Arg0 = term__functor(term__atom("::"), [Arg1, ModeTerm], _),
+		convert_mode(term__coerce(ModeTerm), Mode)
+	->
+		Arg = Arg1,
+		MaybeAnnotation = yes(Mode)
+	;
+		Arg = Arg0,
+		MaybeAnnotation = no
+	).
+
 %-----------------------------------------------------------------------------%
 %
 % Generate the clauses_info for the introduced predicate that we generate
@@ -7603,6 +7730,9 @@
 	prog_out__write_sym_name_and_arity(qualified(Module,Name)/Arity),
 	io__write_string("'.\n").
 
+	% Similar to undeclared_mode_error, but gives less information.
+	% XXX perhaps we should get rid of this, and change the callers to
+	% instead call undeclared_mode_error.
 :- pred undefined_mode_error(sym_name, int, prog_context, string,
 				io__state, io__state).
 :- mode undefined_mode_error(in, in, in, in, di, uo) is det.
@@ -7617,6 +7747,60 @@
 	io__write_string("  `"),
 	prog_out__write_sym_name_and_arity(Name/Arity),
 	io__write_string("' specifies non-existent mode.\n").
+
+	% Similar to undefined_mode_error, but gives more information.
+:- pred undeclared_mode_error(list(mode)::in, prog_varset::in,
+		pred_id::in, pred_info::in, module_info::in, prog_context::in,
+		io__state::di, io__state::uo) is det.
+
+undeclared_mode_error(ModeList, VarSet, PredId, PredInfo, ModuleInfo,
+		Context) -->
+	prog_out__write_context(Context),
+	io__write_string("In clause for "),
+	hlds_out__write_pred_id(ModuleInfo, PredId),
+	io__write_string(":\n"),
+	prog_out__write_context(Context),
+	io__write_string(
+		"  error: mode annotation specifies undeclared mode\n"),
+	prog_out__write_context(Context),
+	io__write_string("  `"),
+	{ strip_builtin_qualifiers_from_mode_list(ModeList,
+		StrippedModeList) },
+	{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
+	{ pred_info_name(PredInfo, Name) },
+	{ MaybeDet = no },
+	mercury_output_mode_subdecl(PredOrFunc,
+		varset__coerce(VarSet),
+		unqualified(Name), StrippedModeList,
+		MaybeDet, Context),
+	io__write_string("'\n"),
+	prog_out__write_context(Context),
+	io__write_string("  of "),
+	hlds_out__write_pred_id(ModuleInfo, PredId),
+	io__write_string(".\n"),
+	globals__io_lookup_bool_option(verbose_errors,
+		VerboseErrors),
+	{ pred_info_all_procids(PredInfo, ProcIds) },
+	( { ProcIds = [] } ->
+		prog_out__write_context(Context),
+		io__write_string(
+		"  (There are no declared modes for this "),
+		hlds_out__write_pred_or_func(PredOrFunc),
+		io__write_string(".)\n")
+	; { VerboseErrors = yes } ->
+		io__write_string(
+		"\tThe declared modes for this "),
+		hlds_out__write_pred_or_func(PredOrFunc),
+		io__write_string(" are the following:\n"),
+		{ OutputProc =
+		    (pred(ProcId::in, di, uo) is det -->
+			io__write_string("\t\t:- mode "),
+			output_mode_decl(ProcId, PredInfo),
+			io__write_string(".\n")) },
+		list__foldl(OutputProc, ProcIds)
+	;
+		[]
+	).
 
 :- pred maybe_undefined_pred_error(sym_name, int, pred_or_func, import_status,
 		bool, prog_context, string, io__state, io__state).
Index: compiler/mode_errors.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/mode_errors.m,v
retrieving revision 1.70
diff -u -d -r1.70 mode_errors.m
--- compiler/mode_errors.m	2001/02/28 12:46:24	1.70
+++ compiler/mode_errors.m	2001/05/15 15:18:02
@@ -178,6 +178,9 @@
 		pred_id, pred_info, module_info, io__state, io__state).
 :- mode report_indistinguishable_modes_error(in, in, in, in, in, di, uo) is det.
 
+:- pred output_mode_decl(proc_id, pred_info, io__state, io__state).
+:- mode output_mode_decl(in, in, di, uo) is det.
+
 %-----------------------------------------------------------------------------%
 %-----------------------------------------------------------------------------%
 
@@ -1187,9 +1190,6 @@
 	prog_out__write_context(OldContext),
 	io__write_string(
 		"  Here is the conflicting mode declaration.\n").
-
-:- pred output_mode_decl(proc_id, pred_info, io__state, io__state).
-:- mode output_mode_decl(in, in, di, uo) is det.
 
 output_mode_decl(ProcId, PredInfo) -->
 	{ pred_info_get_is_pred_or_func(PredInfo, PredOrFunc) },
Index: compiler/module_qual.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/module_qual.m,v
retrieving revision 1.66
diff -u -d -r1.66 module_qual.m
--- compiler/module_qual.m	2001/05/02 17:34:39	1.66
+++ compiler/module_qual.m	2001/05/15 15:52:58
@@ -44,6 +44,14 @@
 :- mode module_qual__qualify_lambda_mode_list(in, out, 
 		in, in, out, di, uo) is det.
 
+	% This is called from make_hlds.m to qualify the modes in a
+	% clause mode annotation.
+:- pred module_qual__qualify_clause_mode_list(list(mode), list(mode),
+		prog_context, mq_info, mq_info,
+		io__state, io__state) is det.
+:- mode module_qual__qualify_clause_mode_list(in, out, 
+		in, in, out, di, uo) is det.
+
 	% This is called from make_hlds.m to qualify an 
 	% explicit type qualification.
 :- pred module_qual__qualify_type_qualification(type, type, prog_context,
@@ -126,6 +134,11 @@
 	{ mq_info_set_error_context(Info0, lambda_expr - Context, Info1) },
 	qualify_mode_list(Modes0, Modes, Info1, Info).
 
+module_qual__qualify_clause_mode_list(Modes0, Modes, Context, Info0, Info) -->
+	{ mq_info_set_error_context(Info0, clause_mode_annotation - Context,
+		Info1) },
+	qualify_mode_list(Modes0, Modes, Info1, Info).
+
 module_qual__qualify_type_qualification(Type0, Type, Context, Info0, Info) -->
 	{ mq_info_set_error_context(Info0, type_qual - Context, Info1) },
 	qualify_type(Type0, Type, Info1, Info).
@@ -1213,6 +1226,7 @@
 	;	func_mode(id)
 	;	(pragma)
 	;	lambda_expr
+	;	clause_mode_annotation
 	;	type_qual
 	;	class(id)
 	;	instance(id).
@@ -1313,6 +1327,8 @@
 	write_id(Id).
 write_error_context2(lambda_expr) -->
 	io__write_string("mode declaration for lambda expression").
+write_error_context2(clause_mode_annotation) -->
+	io__write_string("clause mode annotation").
 write_error_context2(pragma) -->
 	io__write_string("pragma").
 write_error_context2(type_qual) -->
Index: compiler/purity.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/purity.m,v
retrieving revision 1.31
diff -u -d -r1.31 purity.m
--- compiler/purity.m	2001/04/07 14:04:56	1.31
+++ compiler/purity.m	2001/05/15 16:39:09
@@ -14,6 +14,8 @@
 %  The main purpose of this module is check the consistency of the
 %  `impure' and `promise_pure' (etc.) declarations, and to thus report
 %  error messages if the program is not "purity-correct".
+%  This includes treating procedures with different clauses for
+%  different modes as impure, unless promised pure.
 %
 %  This module also does two final parts of type analysis:
 %	- it resolves predicate overloading
@@ -429,13 +431,14 @@
 		{ NumErrors0 = 0 }
 	;   
 		{ pred_info_clauses_info(PredInfo0, ClausesInfo0) },
+		{ pred_info_procids(PredInfo0, ProcIds) },
 		{ clauses_info_clauses(ClausesInfo0, Clauses0) },
 		{ clauses_info_vartypes(ClausesInfo0, VarTypes0) },
 		{ clauses_info_varset(ClausesInfo0, VarSet0) },
 		{ RunPostTypecheck = yes },
 		{ PurityInfo0 = purity_info(ModuleInfo, RunPostTypecheck,
 			PredInfo0, VarTypes0, VarSet0, []) },
-		{ compute_purity(Clauses0, Clauses, pure, Purity,
+		{ compute_purity(Clauses0, Clauses, ProcIds, pure, Purity,
 			PurityInfo0, PurityInfo) },
 		{ PurityInfo = purity_info(_, _, PredInfo1,
 			VarTypes, VarSet, RevMessages) },
@@ -552,18 +555,45 @@
 
 % Infer the purity of a single (non-pragma c_code) predicate
 
-:- pred compute_purity(list(clause), list(clause),
+:- pred compute_purity(list(clause), list(clause), list(proc_id),
 	purity, purity, purity_info, purity_info).
-:- mode compute_purity(in, out, in, out, in, out) is det.
+:- mode compute_purity(in, out, in, in, out, in, out) is det.
 
-compute_purity([], [], Purity, Purity) --> [].
-compute_purity([Clause0|Clauses0], [Clause|Clauses], Purity0, Purity) -->
+compute_purity([], [], _, Purity, Purity) --> [].
+compute_purity([Clause0|Clauses0], [Clause|Clauses], ProcIds,
+		Purity0, Purity) -->
 	{ Clause0 = clause(Ids, Body0 - Info0, Context) },
-	compute_expr_purity(Body0, Body, Info0, no, Bodypurity),
+	compute_expr_purity(Body0, Body, Info0, no, Bodypurity0),
+	% If this clause doesn't apply to all modes of this procedure,
+	% i.e. the procedure has different clauses for different modes,
+	% then we must treat it as impure.
+	{
+		applies_to_all_modes(Clause0, ProcIds)
+	->
+		Clausepurity = (pure)
+	;
+		Clausepurity = (impure)
+	},
+	{ worst_purity(Bodypurity0, Clausepurity, Bodypurity) },
 	{ add_goal_info_purity_feature(Info0, Bodypurity, Info) },
 	{ worst_purity(Purity0, Bodypurity, Purity1) },
 	{ Clause = clause(Ids, Body - Info, Context) },
-	compute_purity(Clauses0, Clauses, Purity1, Purity).
+	compute_purity(Clauses0, Clauses, ProcIds, Purity1, Purity).
+
+:- pred applies_to_all_modes(clause::in, list(proc_id)::in) is semidet.
+
+applies_to_all_modes(clause(ClauseProcIds, _, _), ProcIds) :-
+	(
+		% an empty list here means that the clause applies
+		% to *all* procedures
+		ClauseProcIds = []
+	;
+		% Otherwise the clause applies to the procids in the
+		% list.  Check if this is the same as the procids for
+		% this procedure.
+		list__sort(ClauseProcIds, SortedIds),
+		SortedIds = ProcIds
+	).
 
 :- pred compute_expr_purity(hlds_goal_expr, hlds_goal_expr,
 	hlds_goal_info, bool, purity, purity_info, purity_info).
Index: compiler/notes/compiler_design.html
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/notes/compiler_design.html,v
retrieving revision 1.60
diff -u -d -r1.60 compiler_design.html
--- compiler/notes/compiler_design.html	2001/05/15 07:12:01	1.60
+++ compiler/notes/compiler_design.html	2001/05/15 15:48:39
@@ -184,9 +184,9 @@
 	<li> all types, typeclasses, insts and modes occuring in pred, func,
 	  type, typeclass and mode declarations are module qualified by
 	  module_qual.m.
- 	<li> all types, insts and modes occuring in lambda expressions and
- 	  explicit type qualifications are module qualified in
- 	  make_hlds.m.
+ 	<li> all types, insts and modes occuring in lambda expressions,
+ 	  explicit type qualifications, and clause mode annotations
+	  are module qualified in make_hlds.m.
  	<li> constructors occuring in predicate and function mode declarations
  	  are module qualified during type checking.
  	<li> predicate and function calls and constructors within goals 
Index: doc/reference_manual.texi
===================================================================
RCS file: /home/mercury1/repository/mercury/doc/reference_manual.texi,v
retrieving revision 1.205
diff -u -d -r1.205 reference_manual.texi
--- doc/reference_manual.texi	2001/04/29 07:54:29	1.205
+++ doc/reference_manual.texi	2001/05/15 20:12:18
@@ -534,7 +534,10 @@
 determines which predicate or function the fact belongs to;
 the predicate or function must have been declared
 in a preceding @samp{pred} or @samp{func} declaration in this module.
-The arguments of the head must be valid data-terms.
+The @var{Result} (if any) and the arguments of the @var{Head} must
+be valid data-terms (optionally annotated with a mode qualifier;
+see @pxref{Different clauses for different modes}).
+
 A fact is equivalent to a rule whose body is @samp{true}.
 
 @node Rules
@@ -550,7 +553,9 @@
 function the clause belongs to; the predicate or function must have
 been declared in a preceding @samp{pred} or @samp{func} declaration in
 this module.
-The arguments of the head must be valid data-terms.
+The @var{Result} and the arguments of the @var{Head} must be
+valid data-terms (optionally annotated with a mode qualifier;
+see @pxref{Different clauses for different modes}).
 The @var{Body} must be a valid goal.
 
 @node Goals
@@ -1732,6 +1737,15 @@
 @node Modes
 @chapter Modes
 
+ at menu
+* Insts modes and mode definitions::
+* Predicate and function mode declarations::
+* Different clauses for different modes::
+ at end menu
+
+ at node Insts modes and mode definitions
+ at section Insts, modes, and mode definitions
+
 The @dfn{mode} of a predicate, or function, is a mapping
 from the initial state of instantiation of the arguments of the predicate,
 or the arguments and result of a function,
@@ -1884,6 +1898,9 @@
 and arity in the same module.  Similarly, there must not be more
 than one mode definition with the same name and arity in the same module.
 
+ at node Predicate and function mode declarations
+ at section Predicate and function mode declarations
+
 A @dfn{predicate mode declaration}
 assigns a mode mapping to each argument of a predicate.
 A @dfn{function mode declaration}
@@ -2009,8 +2026,8 @@
 with respect to a given mode declaration}
 if given that the predicates and functions called by @var{p}
 all satisfy their mode declaration constraints,
-there exists an ordering of the literals in the definition of @var{p}
-such that
+there exists an ordering of the conjuncts in each conjunction
+in the clauses of @var{p} such that
 
 @itemize @bullet
 @item
@@ -2040,6 +2057,116 @@
 
 The mode analysis algorithm annotates each call with the mode used.
 
+ at node Different clauses for different modes
+ at section Different clauses for different modes
+
+Because the compiler automatically reorders conjunctions to
+satisfy the modes, it is often possible for a single clause
+to satisfy different modes.  However, occaisionally reordering
+of conjunctions is not sufficient; you may want to write different
+code for different modes.
+
+For example, the usual code for list append
+
+ at example
+	append([], Ys, Ys).
+	append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
+ at end example
+
+ at noindent
+works fine in most modes, but is not very satisfactory for the
+ at samp{append(out, in, in)} mode of append, because although
+every call in this mode only has at most one solution,
+the compiler's determinism inference will not be able to
+infer that.  This means that using the usual code for append in
+this mode will be inefficient, and the overly conservative determinism
+inference may cause spurious determinism errors later.
+
+For this mode, it is better to use a completely different algorithm:
+
+ at example
+	append(Prefix, Suffix, List) :-
+		list__length(List, ListLength),
+		list__length(Suffix, SuffixLength),
+		PrefixLength is ListLength - SuffixLength,
+		list__split_list(PrefixLength, List, Prefix, Suffix).
+ at end example
+
+ at noindent
+However, that code doesn't work in the other modes of append.
+
+To handle such cases, you can use mode annotations on clauses, which
+indicate that particular clauses should only be used for particular modes.
+To specify that a clause only applies to a given mode, each argument
+ at var{Arg} of the clause head should be annotated with the corresponding
+argument mode @var{Mode}, using the @samp{::} mode qualification operator,
+i.e. @samp{@var{Arg} :: @var{Mode}}.
+
+For example, if append was declared as
+
+ at example
+	:- pred append(list(T), list(T), list(T)).
+	:- mode append(in, in, out).
+	:- mode append(out, out, in).
+	:- mode append(in, out, in).
+	:- mode append(out, in, in).
+ at end example
+
+ at noindent
+then you could implement it as
+
+ at example
+	append(L1::in,  L2::in,  L3::out) :- usual_append(L1, L2, L3).
+	append(L1::out, L2::out, L3::in)  :- usual_append(L1, L2, L3).
+	append(L1::in,  L2::out, L3::in)  :- usual_append(L1, L2, L3).
+	append(L1::out, L2::in,  L3::in)  :- other_append(L1, L2, L3).
+
+	usual_append([], Ys, Ys).
+	usual_append([X|Xs], Ys, [X|Zs]) :- usual_append(Xs, Ys, Zs).
+
+	other_append(Prefix, Suffix, List) :-
+		list__length(List, ListLength),
+		list__length(Suffix, SuffixLength),
+		PrefixLength is ListLength - SuffixLength,
+		list__split_list(PrefixLength, List, Prefix, Suffix).
+ at end example
+
+This language feature can be used to write ``impure'' code that
+doesn't have any consistent declarative semantics.  For example,
+you can easily use it to write something similar to Prolog's (in)famous
+var/1 predicate:
+
+ at example
+	:- mode var(in).
+	:- mode var(free>>free).
+	var(_::in) :- fail.
+	var(_::free>>free) :- true.
+ at end example
+
+ at noindent
+As you can see, in this case the two clauses are @emph{not} equivalent.
+
+Because of this possibility, predicates or functions which are defined
+using different code for different modes are by default assumed to be
+impure; the programmer must either (1) carefully ensure that the
+logical meaning of the clauses is the same for all modes,
+in which case a @samp{pragma promise_pure} declaration can be used
+or (2) declare the predicate or function as impure.
+ at xref{Impurity}.
+
+In the example with @samp{append} above, the two ways of implementing
+append do have the same declarative semantics, so we can safely use
+the first approach:
+
+ at example
+	:- pragma promise_pure(append/3).
+ at end example
+
+In the example with @samp{var/1} above, the two clauses have different
+semantics, so the predicate must be declared as impure:
+
+	:- impure pred var(T).
+
 @node Unique modes
 @chapter Unique modes
 
@@ -5068,7 +5195,7 @@
 
 For each Mercury module containing @samp{pragma export} declarations,
 the Mercury implementation will automatically create a header file
-for that module which declares a C function @var{C_Name()}
+for that module which declares a C function @var{C_Name}()
 for each of the @samp{pragma export} declarations.
 Each such C function is the C interface to the specified mode of
 the specified Mercury predicate or function.

-- 
Fergus Henderson <fjh at cs.mu.oz.au>  |  "I have always known that the pursuit
                                    |  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