[m-rev.] For review: State Variables
Simon Taylor
stayl at cs.mu.OZ.AU
Mon May 6 15:51:19 AEST 2002
On 02-May-2002, Ralph Becket <rafe at cs.mu.OZ.AU> wrote:
> Estimated hours taken: 340
> Branches: main
>
> Added state variables to the language. State variables provide a
> lightweight syntax for giving names to sequences that bear some
> resemblance to destructively updated variables found in imperative
> languages. Code using state variables is translated into code that
> uses only ordinary logic variables. State variables are intended
> to simplify code that passes (multiple) in-out state threads, without
> the limitations attached to abusing DCG syntax for this purpose.
> Index: compiler/make_hlds.m
> ===================================================================
> RCS file: /home/mercury1/repository/mercury/compiler/make_hlds.m,v
> retrieving revision 1.408
> diff -u -r1.408 make_hlds.m
> --- compiler/make_hlds.m 7 Apr 2002 10:22:34 -0000 1.408
> +++ compiler/make_hlds.m 2 May 2002 05:30:12 -0000
> @@ -3582,9 +3582,12 @@
> :- mode module_add_clause(in, in, in, in, in, in, in, in, in,
> out, in, out, di, uo) is det.
>
> -module_add_clause(ModuleInfo0, ClauseVarSet, PredOrFunc, PredName, Args, Body,
> +module_add_clause(ModuleInfo0, ClauseVarSet, PredOrFunc, PredName, Args0, Body,
> Status, Context, GoalType, ModuleInfo,
> Info0, Info) -->
> +
> + { Args = expand_dot_colon_state_var_args(Args0) },
> +
That doesn't do the right thing for function clauses.
> @@ -7128,10 +7240,28 @@
> % In the trivial case `X = c', no unravelling occurs.
>
> unravel_unification(term__variable(X), RHS,
> - Context, MainContext, SubContext, VarSet0, Purity,
> - Goal, VarSet, Info0, Info) -->
> + Context, MainContext, SubContext, VarSet0, Purity,
> + Goal, VarSet, Info0, Info, SInfo0, SInfo) -->
> { RHS = term__functor(F, Args, FunctorContext) },
> (
> + % Handle !.X state variable references.
> + { F = term__atom("!.") },
> + { Args = [term__variable(StateVar)] }
> + ->
> + dot(Context, StateVar, Var, VarSet0, VarSet,
> + SInfo0, SInfo),
> + { Goal = svar_unification(Context, X, Var) },
> + { Info = Info0 }
> + ;
> + % Handle !:X state variable references.
> + { F = term__atom("!:") },
> + { Args = [term__variable(StateVar)] }
> + ->
> + colon(Context, StateVar, Var, VarSet0, VarSet,
> + SInfo0, SInfo),
> + { Goal = svar_unification(Context, X, Var) },
> + { Info = Info0 }
The way you've done this the type and mode error messages for
state variable references will be really awful.
For example, code such as
:- func f(int) = int.
f(!.X) = !:X :-
!:X = !.X / 2.
will be transformed into
:- func f(int) = float.
f(HeadVar__1) = HeadVar__2 :-
HeadVar__1 = STATE_VAR_X_0,
HeadVar__2 = STATE_VAR_X,
V_1 = STATE_VAR_X_2,
V_2 = STATE_VAR_X_0,
V_1 = V_2 / 1.0.
STATE_VAR_X = STATE_VAR_X_2.
Type and mode error message for the call to '/' will refer to
the introduced `V_' variables, not the state variables.
It would be better to expand state variable references before calling
make_fresh_arg_vars.
> @@ -7246,16 +7380,18 @@
> { parse_some_vars_goal(IfTerm, VarSet0, Vars,
> IfParseTree, VarSet11) }
You should allow state variable quantifiers on if-then-elses.
> @@ -7732,19 +7890,21 @@
>
> :- pred get_conj(goal, prog_substitution, list(hlds_goal), prog_varset,
> list(hlds_goal), prog_varset, transform_info, transform_info,
> - io__state, io__state).
> -:- mode get_conj(in, in, in, in, out, out, in, out, di, uo) is det.
> + svar_info, svar_info, io__state, io__state).
> +:- mode get_conj(in, in, in, in, out, out, in, out, in, out, di, uo) is det.
>
> -get_conj(Goal, Subst, Conj0, VarSet0, Conj, VarSet, Info0, Info) -->
> +get_conj(Goal, Subst, Conj0, VarSet0, Conj, VarSet, Info0, Info,
> + SInfo0, SInfo) -->
> (
> { Goal = (A,B) - _Context }
> ->
> - get_conj(B, Subst, Conj0, VarSet0, Conj1, VarSet1,
> - Info0, Info1),
> - get_conj(A, Subst, Conj1, VarSet1, Conj, VarSet, Info1, Info)
> + get_conj(A, Subst, Conj0, VarSet0, Conj1, VarSet1,
> + Info0, Info1, SInfo0, SInfo1),
> + get_conj(B, Subst, Conj1, VarSet1, Conj, VarSet,
> + Info1, Info, SInfo1, SInfo)
> ;
> transform_goal(Goal, VarSet0, Subst, Goal1, VarSet,
> - Info0, Info),
> + Info0, Info, SInfo0, SInfo),
> { goal_to_conj_list(Goal1, ConjList) },
> { list__append(ConjList, Conj0, Conj) }
> ).
You're now building Conj in reverse, so ConjList needs to be reversed
before adding it to Conj0 (same for get_par_conj and get_disj).
> +%------------------------------------------------------------------------------%
> +
> + % This synonym improves code legibility.
> + %
> +:- type svar == prog_var.
It would probably be better to do
:- type svar == var(svar_type).
:- type svar_type ---> svar_type.
This would help avoid confusing state variables with ordinary variables.
> +:- pred dot(prog_context, svar, prog_var,
> + prog_varset, prog_varset, svar_info, svar_info, io, io).
> +:- mode dot(in, in, out, in, out, in, out, di, uo) is det.
This predicate needs documentation and a more descriptive name.
> +:- pred colon(prog_context, svar, prog_var,
> + prog_varset, prog_varset, svar_info, svar_info, io, io).
> +:- mode colon(in, in, out, in, out, in, out, di, uo) is det.
Ditto.
> +colon(Context, StateVar, Var, VarSet0, VarSet, SInfo0, SInfo, IO0, IO) :-
> +
> + ( if SInfo0 ^ ctxt = in_head then
> +
> + ( if SInfo0 ^ colon ^ elem(StateVar) = Var0 then
> + Var = Var0,
> + VarSet = VarSet0,
> + SInfo = SInfo0,
> + IO = IO0
> + else
> + new_final_state_var(StateVar, Var,
> + VarSet0, VarSet, SInfo0, SInfo),
> + IO = IO0
> + )
> +
> + else
> +
> + ( if SInfo0 ^ colon ^ elem(StateVar) = Var0 then
> + Var = Var0,
> + VarSet = VarSet0,
> + SInfo = SInfo0 `with_updated_svar` StateVar,
> + IO = IO0
> + else
> + Var = StateVar,
> + VarSet = VarSet0,
> + SInfo = SInfo0,
> + Name = varset__lookup_name(VarSet0, StateVar),
> + prog_out__write_context(Context, IO0, IO1),
> + report_warning(string__format("\
> +Error: reference to !:%s, no such state variable in scope.\n", [s(Name)]),
> + IO1, IO2),
> + ( if SInfo0 ^ external_dot `contains` StateVar then
> + prog_out__write_context(Context, IO2, IO3),
> + report_warning(string__format("\
> + (although state variable !.%s is in scope.)\n", [s(Name)]),
> + IO3, IO)
> + else
> + IO = IO2
> + )
> + )
> + ).
The error message for references to !:X in a lambda or if-then-else
expression is pretty ordinary.
> + % We have to conjoin the head and body and add unifiers to tie up all
> + % the final values of the state variables to the head variables.
> + %
> +:- pred finish_head_and_body(prog_context, svar_map,
> + hlds_goal, hlds_goal, hlds_goal, svar_info).
> +:- mode finish_head_and_body(in, in, in, in, out, in) is det.
> +
> +finish_head_and_body(Context, FinalSVarMap, Head, Body, Goal, SInfo) :-
> + goal_info_init(Context, GoalInfo),
> + goal_to_conj_list(Head, HeadGoals),
> + goal_to_conj_list(Body, BodyGoals),
> + Unifiers = svar_unifiers(Context, FinalSVarMap, SInfo ^ dot),
> + conj_list_to_goal(HeadGoals ++ BodyGoals ++ Unifiers, GoalInfo, Goal).
It's probably better to substitute in the head rather than adding
unifications (adding too many variables will cause slow compilation).
+ % When we finish a call, we're either still inside the
+ % atomic formula, in which case we simply propagate the set of
+ % "updated" state variables, or we've just emerged, in which
case
+ % we need to set up the svar_info for the next conjunct.
+ %
+:- pred finish_call(prog_varset, prog_varset, svar_info, svar_info).
+:- mode finish_call(in, out, in, out) is det.
How can you still be inside an atomic formula when you've finished a
call?
> + % We have to add unifiers to the Then and Else clauses of an
> + % if-then-else to make sure all the state variables match up.
> + %
> + % We construct new mappings for the state variables and then
> + % add unifiers.
> + %
> +:- pred finish_if_then_else(prog_context,
> + hlds_goal, hlds_goal, hlds_goal, hlds_goal,
> + prog_varset, prog_varset, svar_info, svar_info, svar_info).
> +:- mode finish_if_then_else(in, in, out, in, out, in, out, in, in, out) is det.
> +
> +finish_if_then_else(Context, Then0, Then, Else0, Else, VarSet0, VarSet,
> + SInfoT, SInfoE, SInfo) :-
> + SInfo0 = SInfoT,
> + N = int__max(SInfoT ^ num, SInfoE ^ num),
> + next_svar_info(N, VarSet0, VarSet, SInfo0, SInfo),
> +
> + goal_info_init(Context, GoalInfo),
> +
> + goal_to_conj_list(Then0, ThenGoals0),
> + ThenUnifiers = svar_unifiers(Context, SInfo ^ dot, SInfoT ^ dot),
> + conj_list_to_goal(ThenGoals0 ++ ThenUnifiers, GoalInfo, Then),
> +
> + goal_to_conj_list(Else0, ElseGoals0),
> + ElseUnifiers = svar_unifiers(Context, SInfo ^ dot, SInfoE ^ dot),
> + conj_list_to_goal(ElseGoals0 ++ ElseUnifiers, GoalInfo, Else).
This doesn't handle code like the following properly:
( p(!X) ->
q
;
r(!X)
).
It would also be nice to avoid adding lots of extra variables at the
end of each branch (you do this for disjunctions as well). See how
prog_io_dcg.m does this.
> +%------------------------------------------------------------------------------%
> +
> +:- pred next_svar_info(int, prog_varset, prog_varset, svar_info, svar_info).
> +:- mode next_svar_info(in, in, out, in, out) is det.
Documentation please.
> +%------------------------------------------------------------------------------%
> +
> + % We assume that a negation updates all state variables in scope,
> + % so we construct new mappings for the state variables and then
> + % add unifiers from their pre-negated goal mappings.
> + %
> +:- pred finish_negation(prog_context, hlds_goal, hlds_goal,
> + prog_varset, prog_varset, svar_info, svar_info, svar_info).
> +:- mode finish_negation(in, in, out, in, out, in, in, out) is det.
> +
> +finish_negation(Context, Goal0, Goal, VarSet0, VarSet,
> + SInfoBefore, SInfoNeg, SInfo) :-
> + SInfo0 = SInfoBefore,
> + N = SInfoNeg ^ num,
> + next_svar_info(N, VarSet0, VarSet, SInfo0, SInfo),
> +
> + goal_info_init(Context, GoalInfo),
> +
> + goal_to_conj_list(Goal0, Goals0),
> + Unifiers = svar_unifiers(Context, SInfo ^ dot, SInfo0 ^ dot),
> + conj_list_to_goal(Goals0 ++ Unifiers, GoalInfo, Goal).
You should be able to do this without adding unifications.
I think you're making life difficult for yourself by trying
to maintain the `dot' and `colon' mappings all the time, rather
than generating a `colon' mapping only when a reference to `!:X'
is seen in an atom (as prog_io_dcg.m does).
> @@ -220,10 +242,29 @@
> parse_goal(A0, V0, A, V1),
> parse_goal(B0, V1, B, V).
>
> -parse_goal_2("some", [Vars0, A0], V0, some(Vars, A), V):-
> - parse_list_of_vars(Vars0, Vars1),
> - list__map(term__coerce_var, Vars1, Vars),
> - parse_goal(A0, V0, A, V).
> +parse_goal_2("some", [QVars, A0], V0, GoalExpr, V):-
You also need to modify the similar code in prog_io_dcg.m.
You should also handle state variables in the quantifiers
on if-then-elses.
To be continued.
Simon.
--------------------------------------------------------------------------
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