[m-dev.] for review: add exception__try_store
Fergus Henderson
fjh at cs.mu.OZ.AU
Thu Feb 22 03:11:43 AEDT 2001
For the main branch only, not the release branch.
----------
Estimated hours taken: 2.5
library/exception.m:
Add new predicate `try_store'.
This is like `try_io', but for stores.
tests/hard_coded/exceptions/Mmakefile:
tests/hard_coded/exceptions/tricky_try_store.m:
tests/hard_coded/exceptions/tricky_try_store.exp:
Add a test case for `try_store'.
NEWS:
Mention the new predicate.
Workspace: /home/hg/fjh/mercury
Index: library/exception.m
===================================================================
RCS file: /home/mercury1/repository/mercury/library/exception.m,v
retrieving revision 1.43
diff -u -d -r1.43 exception.m
--- library/exception.m 2001/02/04 04:10:34 1.43
+++ library/exception.m 2001/02/21 16:01:58
@@ -19,7 +19,7 @@
%-----------------------------------------------------------------------------%
:- module exception.
:- interface.
-:- import_module std_util, list, io.
+:- import_module std_util, list, io, store.
%
% throw(Exception):
@@ -84,6 +84,17 @@
out(cannot_fail), di, uo) is cc_multi.
%
+% try_store(Goal, Result, Store_0, Store):
+% Just like try_io, but for stores rather than io__states.
+%
+:- pred try_store(pred(T, store(S), store(S)),
+ exception_result(T), store(S), store(S)).
+:- mode try_store(pred(out, di, uo) is det,
+ out(cannot_fail), di, uo) is cc_multi.
+:- mode try_store(pred(out, di, uo) is cc_multi,
+ out(cannot_fail), di, uo) is cc_multi.
+
+%
% try_all(Goal, ResultList):
% Operational semantics:
% Try to find all solutions to Goal(R), using backtracking.
@@ -165,6 +176,13 @@
:- mode try_io(in(bound(cc_multi)), pred(out, di, uo) is cc_multi,
out(cannot_fail), di, uo) is cc_multi.
+:- pred try_store(determinism, pred(T, store(S), store(S)),
+ exception_result(T), store(S), store(S)).
+:- mode try_store(in(bound(det)), pred(out, di, uo) is det,
+ out(cannot_fail), di, uo) is cc_multi.
+:- mode try_store(in(bound(cc_multi)), pred(out, di, uo) is cc_multi,
+ out(cannot_fail), di, uo) is cc_multi.
+
:- pred try_all(determinism, pred(T), list(exception_result(T))).
:- mode try_all(in(bound(det)), pred(out) is det,
out(try_all_det)) is cc_multi.
@@ -198,7 +216,7 @@
:- mode get_determinism(pred(out) is cc_nondet, out(bound(cc_nondet)))
is cc_multi.
-:- pred get_determinism_2(pred(T, io__state, io__state), determinism).
+:- pred get_determinism_2(pred(T, S, S), determinism).
:- mode get_determinism_2(pred(out, di, uo) is det, out(bound(det)))
is cc_multi.
:- mode get_determinism_2(pred(out, di, uo) is cc_multi, out(bound(cc_multi)))
@@ -476,6 +494,54 @@
wrap_success(Goal, R)),
wrap_exception, Result)),
AccPred, Acc0, Acc).
+
+% We need to switch on the Detism argument
+% for the same reason as above.
+
+try_store(StoreGoal, Result) -->
+ { get_determinism_2(StoreGoal, Detism) },
+ try_store(Detism, StoreGoal, Result).
+
+ % Store0 is not really unique in the calls to unsafe_promise_unique
+ % below, since it is also used in the calls to handle_store_result.
+ % But it is safe to treat it as if it were unique, because the
+ % other reference is only used in the case when an exception is
+ % thrown, and in that case the declarative semantics of this
+ % predicate say that the final store returned is unspecified.
+try_store(det, StoreGoal, Result, Store0, Store) :-
+ Goal = (pred({R, S}::out) is det :-
+ unsafe_promise_unique(Store0, S0),
+ StoreGoal(R, S0, S)),
+ try(det, Goal, Result0),
+ handle_store_result(Result0, Result, Store0, Store).
+try_store(cc_multi, StoreGoal, Result, Store0, Store) :-
+ Goal = (pred({R, S}::out) is cc_multi :-
+ unsafe_promise_unique(Store0, S0),
+ StoreGoal(R, S0, S)),
+ try(cc_multi, Goal, Result0),
+ handle_store_result(Result0, Result, Store0, Store).
+
+:- pred handle_store_result(exception_result({T, store(S)})::in(cannot_fail),
+ exception_result(T)::out(cannot_fail),
+ store(S)::in, store(S)::uo) is det.
+handle_store_result(Result0, Result, Store0, Store) :-
+ (
+ Result0 = succeeded({Res, S1}),
+ Result = succeeded(Res),
+ % S1 is now unique because the only other reference to the
+ % store was from Store0, which we're throwing away here
+ unsafe_promise_unique(S1, Store)
+ ;
+ Result0 = exception(E0),
+ % We need to make a copy of the exception object, in case
+ % it contains a value returned from store__extract_ref_value.
+ % See tests/hard_coded/exceptions/tricky_try_store.m.
+ copy(E0, E),
+ Result = exception(E),
+ % Store0 is now unique because the only other reference to
+ % the store was from the goal which just threw an exception.
+ unsafe_promise_unique(Store0, Store)
+ ).
try_io(IO_Goal, Result) -->
{ get_determinism_2(IO_Goal, Detism) },
Index: NEWS
===================================================================
RCS file: /home/mercury1/repository/mercury/NEWS,v
retrieving revision 1.201
diff -u -d -r1.201 NEWS
--- NEWS 2001/02/19 10:41:47 1.201
+++ NEWS 2001/02/21 16:08:22
@@ -1,3 +1,15 @@
+NEWS since Mercury release 0.10
+-------------------------------
+Note that Mercury 0.10 has not actually been officially announced;
+this section documents changes in the development version of
+Mercury since we established a feature freeze for Mercury 0.10
+and split development of that version off onto a separate branch
+of our CVS repository (the `version-0_10_y' branch).
+
+Changes to the Mercury standard library:
+* The exception module has a new predicate `try_store', which is
+ like `try_io', but which works with stores rather than io__states.
+
NEWS for Mercury release 0.10:
------------------------------
Index: tests/hard_coded/exceptions/Mmakefile
===================================================================
RCS file: /home/mercury1/repository/tests/hard_coded/exceptions/Mmakefile,v
retrieving revision 1.6
diff -u -d -r1.6 Mmakefile
--- tests/hard_coded/exceptions/Mmakefile 2001/01/10 05:27:53 1.6
+++ tests/hard_coded/exceptions/Mmakefile 2001/02/21 16:03:26
@@ -13,11 +13,17 @@
-include ../../Mmake.params
PROGS = test_exceptions.m test_uncaught_exception.m test_exceptions_func.m \
- test_try_all.m
+ test_try_all.m tricky_try_store.m
#
# XXX the following tests are not enabled because we do not pass them yet:
# test_memo.m test_loop_check.m
# (those two tests test the combination of tabling and exceptions).
+#
+# Also currently the compiler has a bug where it generates
+# static ground terms even for things with `di' modes;
+# tricky_try_store.m contains a work-around for that,
+# which should be deleted once that bug is fixed.
+#
depend: $(PROGS:.m=.depend)
all: $(PROGS:.m=)
Index: tests/hard_coded/exceptions/tricky_try_store.exp
===================================================================
RCS file: tricky_try_store.exp
diff -N tricky_try_store.exp
--- /dev/null Thu Mar 30 14:06:13 2000
+++ tricky_try_store.exp Thu Feb 22 02:52:05 2001
@@ -0,0 +1,3 @@
+Result = exception(univ_cons(["initial"]))
+Result = exception(univ_cons(["initial"]))
+Val = ["updated"]
Index: tests/hard_coded/exceptions/tricky_try_store.m
===================================================================
RCS file: tricky_try_store.m
diff -N tricky_try_store.m
--- /dev/null Thu Mar 30 14:06:13 2000
+++ tricky_try_store.m Thu Feb 22 03:04:35 2001
@@ -0,0 +1,54 @@
+% There is one tricky aspect with exception__try_store. If we're not
+% careful, the user could use `store__extract_ref_value', which destroys
+% the store and extracts the referenced value without making a copy.
+% The user could then throw the extracted value, and if the handler gets
+% both the extracted value and a unique version of the store, then it
+% can update the reference, which would modify the extracted value,
+% breaking referential transparency.
+%
+% In other words, with a naive implementation of `try_store',
+% the following program could print out
+%
+% Result = exception(initial)
+% Result = exception(updated)
+%
+% To avoid this, the implementation of try_store must make a copy of the
+% thrown object before returning it from try_store.
+%
+% This test case checks that the implementation of try_store does the
+% right thing in this tricky case.
+
+:- module tricky_try_store.
+:- interface.
+:- import_module io.
+:- pred main(io__state::di, io__state::uo) is cc_multi.
+
+:- implementation.
+:- import_module exception, store, list.
+
+:- pred tricky(store__ref(T, S), int, store(S), store(S)).
+:- mode tricky(in, out, di, uo) is det.
+
+tricky(Key, _Output, Store0, _Store) :-
+ store__extract_ref_value(Store0, Key, Value),
+ throw(Value).
+
+main -->
+ { store__new(Store0) },
+ { store__new_ref(mklist("initial"), Key, Store0, Store1) },
+ { store__arg_ref(Key, 0, SubKey, Store1, Store2) },
+ { exception__try_store(tricky(Key), Result, Store2, Store3) },
+ print("Result = "), print(Result), nl,
+ { store__set_ref_value(SubKey, "updated", Store3, Store) },
+ print("Result = "), print(Result), nl,
+ { store__extract_ref_value(Store, Key, Val) },
+ print("Val = "), print(Val), nl.
+
+% XXX the current compiler has a bug whereby it generates static ground terms
+% even for things that are used in `di' modes. To avoid that bug,
+% we use the following hack -- a `pragma no_inline' function --
+% to ensure that `["initial"]' doesn't get stored as a static constant.
+:- func mklist(T) = list(T).
+:- mode mklist(di) = uo is det.
+:- pragma no_inline(mklist/1).
+mklist(X) = [X].
--
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-developers mailing list
Post messages to: mercury-developers at cs.mu.oz.au
Administrative Queries: owner-mercury-developers at cs.mu.oz.au
Subscriptions: mercury-developers-request at cs.mu.oz.au
--------------------------------------------------------------------------
More information about the developers
mailing list