<p>Hi, all.</p>
<p>Though I'm not a native english speaker, but I think that the keyword 'do' looks so imperative. It's a good idea to use another keyword to make it look more declarative, for example, like 'for ... that P(....)' .</p>
<div class="gmail_quote">在 2014-1-18 下午12:00,"Mark Brown" <<a href="mailto:mark@mercurylang.org">mark@mercurylang.org</a>>写道:<br type="attribution"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
Hi everyone,<br>
<br>
Here is a concrete proposal for "inductive goals" in Mercury. I'm<br>
going to avoid calling these "loops", since logic programmers<br>
understandably take a dim view of these (hint: if it terminates, it's<br>
not really a loop). The goals are called inductive because they are<br>
logically equivalent to the conjunction of a sequence of goals, with<br>
this sequence being inductively defined. They could also be called<br>
"for" goals, since that is the keyword they begin with.<br>
<br>
This follows on from the "Adding Loops to Mercury" thread earlier this<br>
month, but I've started a new thread for this specific proposal. This<br>
is to put the design on the record. I'm not planning to implement it<br>
myself at this time.<br>
<br>
Would this meet people's requirements?<br>
<br>
Cheers,<br>
Mark.<br>
<br>
<br>
Inductive Goals<br>
----------------------<br>
<br>
1. Background<br>
<br>
The declarative semantics is essentially that of the "more<br>
complicated" version of sequence quantifiers that Fergus Henderson<br>
writes about [1], which are based on Peter Schachte's Sequence<br>
Quantification [2]. The operational semantics are derived from<br>
Sebastian Brand's generic inline iteration proposal [3], which in turn<br>
is derived from Joachim Schimpf's Logical Loops [4].<br>
<br>
The main challenge for Mercury is to perform the Logical Loops<br>
translation in such a way that precise determinisms are preserved.<br>
This proposal achieves that by using available mode information to<br>
direct the translation.<br>
<br>
[1] <a href="http://lists.mercurylang.org/pipermail/developers/2000-December/010812.html" target="_blank">http://lists.mercurylang.org/pipermail/developers/2000-December/010812.html</a><br>
[2] <a href="http://lists.mercurylang.org/pipermail/developers/2014-January/015962.html" target="_blank">http://lists.mercurylang.org/pipermail/developers/2014-January/015962.html</a><br>
[3] <a href="http://lists.mercurylang.org/pipermail/developers/2005-December/013919.html" target="_blank">http://lists.mercurylang.org/pipermail/developers/2005-December/013919.html</a><br>
[4] <a href="http://www.eclipseclp.org/reports/index.html" target="_blank">http://www.eclipseclp.org/reports/index.html</a><br>
<br>
2. Sequences<br>
<br>
We start by defining two kinds of sequence: which I will call map<br>
sequences and fold sequences. For a given sequence of values, the map<br>
sequence is just those values in the same order, while the fold<br>
sequence is the sequence of consecutive pairs of values. So for the<br>
values 1, 2, 3, 4, 5, we have:<br>
<br>
elements of map sequence: 1, 2, 3, 4, 5<br>
elements of fold sequence: (1, 2), (2, 3), (3, 4), (4, 5)<br>
<br>
The actual values involved will be defined inductively by the goal body.<br>
<br>
Two or more sequences "correspond" if they are all of the same length.<br>
Note that this implies that fold sequences contain one more value than<br>
corresponding map sequences, since they include both the initial and<br>
final value.<br>
<br>
3. Syntax<br>
<br>
Inductive goals are written like this:<br>
<br>
for $sequence, ... mismatch $mismatch do $body<br>
<br>
The intuitive meaning of this is to iterate over all sequences in<br>
lockstep, executing $body for each set of corresponding elements, and<br>
executing $mismatch if the sequences don't correspond. The mismatch<br>
goal is optional, and defaults to throwing a software error. If given,<br>
the mismatch goal is not allowed to succeed (i.e., it must have<br>
determinism failure or erroneous).<br>
<br>
Sequences are written in one of the following forms:<br>
<br>
$X maps $Xs<br>
[$Current, $Next] folds [$First, $Last]<br>
[$A0, $A]<br>
<br>
The first says that $X is an element in the map sequence defined by<br>
$Xs. The second says that ($Current, $Next) is an element in the fold<br>
sequence whose first value is $First and whose last value is $Last.<br>
The third is just shorthand for '[$A0, $A] folds [$A0, $A]', and can<br>
be used if $A0 and $A are Mercury variables (the other forms can be<br>
used with arbitrary Mercury expressions).<br>
<br>
Mercury variables on the left hand side of 'maps' or 'folds' are<br>
quantified in the body. Those on the right hand side are free (i.e.,<br>
non-local to the inductive goal). For the shorthand form, $A0 and $A<br>
stand for two pairs of variables with the same names, one pair of<br>
which is quantified in the body while the other is free.<br>
<br>
4. Examples<br>
<br>
Here's how to implement a few of the existing list.m library predicates.<br>
<br>
all_true(P, Xs) :- for X maps Xs do P(X).<br>
<br>
map(P, Xs, Ys) :- for X maps Xs, Y maps Ys do P(X, Y).<br>
<br>
length(Xs, N) :- for _ maps Xs, [I, I + 1] folds [0, N] do true.<br>
<br>
map_corresponding3_foldl(P, As, Bs, Cs, Ds, !S) :-<br>
for A maps As, B maps Bs, C maps Cs, D maps Ds, [!S]<br>
mismatch throw("list.map_corresponding3_foldl: length mismatch")<br>
do P(A, B, C, D, !S).<br>
<br>
The code for map3_foldl is the same as for map_corresponding3_foldl,<br>
except that the mismatch clause isn't needed. These predicates are<br>
effectively different modes of the same inductive goal.<br>
<br>
Sequences that can fail can be useful:<br>
<br>
take(N, List, Start) :-<br>
for<br>
[I, I + 1] folds [0, N],<br>
[[X | Xs0], Xs0] folds [List, _], % can fail<br>
X maps Start<br>
do true.<br>
<br>
The do goal can refer to non-local variables, although it is not<br>
allowed to bind them. This example refers to the non-local Stream<br>
variable to print the elements in a list:<br>
<br>
write_elements(Stream, Xs, !IO) :-<br>
for X maps Xs, [!IO] do<br>
write(Stream, X, !IO),<br>
nl(Stream, !IO).<br>
<br>
Filtering is not done by the construct itself, but is easily done in the body:<br>
<br>
filter_map_corresponding(F, As, Bs) = Cs :-<br>
for A maps As, B maps Bs, [Cs1, Cs0] folds [Cs, []]<br>
mismatch throw("filter_map_corresponding/2: length mismatch")<br>
do<br>
( if C = F(A, B) then<br>
Cs1 = [C | Cs0]<br>
else<br>
Cs1 = Cs0<br>
).<br>
<br>
Here's an example that calls P on all the unordered pairs of elements<br>
of list Xs, with accumulator !A:<br>
<br>
for [[X | Tail], Tail] folds [Xs, []], [!A] do<br>
for Y maps Tail, [!A] do<br>
P(X, Y, !A).<br>
<br>
Here's an example of iterating over a sequence defined by the<br>
functions empty/0, next/1 and value/1:<br>
<br>
for [S, next(S)] folds [Start, empty], [!IO] do write(value(S), !IO).<br>
<br>
Here's a similar example using function empty/0 and predicate next/3:<br>
<br>
for [!S] folds [Start, empty], [!IO] do next(I, !S), write(I, !IO).<br>
<br>
One thing that inductive goals can't do is use a predicate to decide<br>
if the sequence is empty. The test for whether the sequence is empty<br>
is done by unifying the sequence with a specific value. So an integer<br>
sequence can end with a test like I = N, but not one like I > N. The<br>
same effect, however, can be obtained by adding an extra sequence of<br>
booleans that say whether the induction should continue:<br>
<br>
% Counts while $Test succeeds.<br>
for [I, I + 1] folds [0, _], [!Continue] folds [yes, no] do<br>
( if $Test then $Body else !:Continue = no ).<br>
<br>
As a final example, inductive goals can also be used to generate<br>
sequences of all lengths. This goal generates all lists of 0s and 1s:<br>
<br>
for X maps Xs do ( X = 0 ; X = 1 ).<br>
<br>
5. Semantics<br>
<br>
The meaning of the inductive goal is given by universally quantifying<br>
over all sequence positions. So for example the goal:<br>
<br>
for<br>
$X maps $Xs,<br>
[$Current, $Next] folds [$First, $Last],<br>
[$A0, $A],<br>
...<br>
do<br>
$Body<br>
<br>
means the following:<br>
<br>
all [I] (<br>
$X is the I'th element of $Xs /\<br>
$Current is the I'th value of the first fold sequence /\<br>
$Next is the (I+1)'th value of the first fold sequence /\<br>
$A0 is the I'th value of the second fold sequence /\<br>
$A is the (I + 1)'th value of the second fold sequence /\<br>
...<br>
=><br>
$Body<br>
)<br>
<br>
6. Types and Modes<br>
<br>
Types are determined by the type signatures of the sequence<br>
expressions, which are as follows:<br>
<br>
all [T] (T maps list(T))<br>
all [T] ([T, T] folds [T, T])<br>
<br>
These ensure each sequence is typed consistently, but allow different<br>
sequences to have unrelated types.<br>
<br>
Like types, modes are determined by the mode signatures of the<br>
sequence expressions. For each form of sequence there is a selection<br>
of modes to choose from. This choice is made for each sequence<br>
independently. The modes are as follows:<br>
<br>
% Input modes:<br>
in(I) maps in(list(I))<br>
[in(I), out(I)] folds [in(I), in(I)]<br>
<br>
% Output modes:<br>
out(I) maps out(list(I))<br>
[in(I), out(I)] folds [in(I), out(I)]<br>
[mdi, muo] folds [mdi, muo]<br>
[di, uo] folds [di, uo]<br>
[out(I), in(I)] folds [out(I), in(I)]<br>
<br>
Sequences with input modes determine the number of iterations that are<br>
generated. Sequences with output modes work with any number of<br>
iterations.<br>
<br>
In order to schedule an inductive goal, the mode analyser needs to<br>
schedule all sequences at the same time. This involves checking that,<br>
for each fold sequence, at least one of the terms on the right hand<br>
side is instantiated. Scheduling should probably be delayed until as<br>
many sequences are in the input mode as possible, which will ensure<br>
the strongest determinism result. Note that all inst variables will be<br>
bound when the goal is scheduled, so inst variables won't need to<br>
appear in generated code.<br>
<br>
7. Determinism<br>
<br>
In Mercury, the determinism of a goal or expression is specified by<br>
the answers to three questions:<br>
<br>
- Can the goal or expression fail?<br>
- Can the goal produce multiple solutions? (Expressions aren't<br>
allowed to do this.)<br>
- Does the goal or expression have to be evaluated in a committed<br>
choice context?<br>
<br>
For any of these questions, if the answer is yes for the body goal or<br>
the head expressions on the right hand side of the sequence operators,<br>
then it is yes for the inductive goal as a whole. This is also true<br>
for the expressions on the left hand side of the sequence operators,<br>
with one exception. The exception is that, in the input mode for<br>
'folds', the first argument on the left hand side is never tested<br>
against the value of the second argument on the right hand side; had<br>
it taken that value, the inductive goal would already have terminated.<br>
So this expression does not itself need to be proven never to fail. If<br>
it does actually ever fail on some other value, the inductive goal<br>
either fails or the mismatch goal is executed (behaviour in this<br>
respect is undefined - in the translation below it fails if all<br>
sequences are output, but throws the mismatch exception in all other<br>
modes).<br>
<br>
Aside from the previous paragraph, the only other way an inductive<br>
goal can fail is if the mismatch goal fails, which can only occur if<br>
it has a failure determinism. The only other way an inductive goal can<br>
produce multiple solutions is if all sequences are in the output mode.<br>
There are no other circumstances in which an inductive goal must be<br>
evaluated in a committed choice context.<br>
<br>
8. Translation<br>
<br>
Inductive goals are translated at compile time into a call to a<br>
compiler generated predicate. The predicate is passed the following<br>
arguments:<br>
<br>
- One argument for each non-local variable occurring in the body<br>
or in the mismatch goal.<br>
- One argument for each map sequence.<br>
- Two arguments for each fold sequence.<br>
<br>
In the first case, the arguments are the non-local variable<br>
themselves. In the other two cases, the arguments are those<br>
expressions appearing on the right hand side of the relevant sequence.<br>
<br>
For example, Separator is non-local in the following goal:<br>
<br>
for X maps Xs, [!IO] do write(X, !IO), write_string(Separator, !IO)<br>
<br>
This is translated into the call:<br>
<br>
$predname(Separator, Xs, !IO)<br>
<br>
where $predname is the name of the generated predicate.<br>
<br>
In the signature of the generated predicate, the types of the<br>
parameters are determined directly from the types of the arguments<br>
listed above. For each of the non-local arguments the mode is 'in(I)',<br>
where I is the inst of the non-local at the time the call is<br>
scheduled. For the other arguments, the mode is determined by the<br>
selected mode of the sequence in question. The determinism can be<br>
inferred according to the rules given earlier.<br>
<br>
For the example above, $predname would be given the following signature:<br>
<br>
:- pred $predname(string::in, list(T)::in, io::di, io::uo) is det.<br>
<br>
The head of the generated clause uses the same names for the non-local<br>
head variables as are used in the original goal. For map sequences,<br>
the head variables are named _M1, _M2, etc, and for fold sequences<br>
they are named _Fa1, _Fb1, _Fa2, _Fb2, etc.<br>
<br>
For each sequence we define two goals, the end goal and the step goal.<br>
The end goal defines the end of the sequence, while the step goal<br>
defines each step along the way. For i'th map sequence '$X maps $Xs',<br>
the goals are:<br>
<br>
end goal: _Mi = []<br>
step goal: _Mi = [$X | _Mri]<br>
<br>
where _Mri is a fresh variable. For the i'th fold sequence '[$Current,<br>
$Next] folds [$First, $Last]', the goals are:<br>
<br>
end goal: _Fai = _Fbi<br>
step goal: _Fai = $Current<br>
<br>
The generated clause makes one recursive call to itself. The<br>
non-locals are passed as is. For the i'th map sequence, the recursive<br>
argument is _Mri. For the i'th fold sequence, the two arguments are<br>
$Next and _Fbi.<br>
<br>
The general form of the generated clause body depends on which of the<br>
sequences are used in an input mode, and which are output. If there is<br>
at least one input sequence, the body has this general form:<br>
<br>
( if<br>
conjunction of input end goals<br>
then<br>
conjunction of output end goals<br>
else if<br>
conjunction of input step goals<br>
then<br>
conjunction of output step goals,<br>
$Body,<br>
recursive call<br>
else<br>
$Mismatch<br>
).<br>
<br>
If all of the sequences are output, the body has this general form:<br>
<br>
(<br>
conjunction of end goals<br>
;<br>
conjunction of step goals,<br>
$Body,<br>
recursive call<br>
).<br>
<br>
The example from above is repeated here:<br>
<br>
for X maps Xs, [!IO] do write(X, !IO), write_string(Separator, !IO)<br>
<br>
For this inductive goal, which is called with the first sequence in<br>
the input mode, we generate the following clause (with state variable<br>
!IO expanded out to _IO_i):<br>
<br>
$predname(Separator, _M1, _Fa1, _Fb1) :-<br>
( if<br>
_M1 = []<br>
then<br>
_Fa1 = _Fb1<br>
else if<br>
_M1 = [X | _Mr1]<br>
then<br>
_Fa1 = _IO_0,<br>
write(X, _IO_0, _IO_1),<br>
write_string(Separator, _IO_1, _IO_2),<br>
$predname(Separator, _Mr1, _IO_2, _Fb1)<br>
else<br>
error("length mismatch in inductive goal")<br>
).<br>
<br>
9. Example translations<br>
<br>
Here are the translations for a few of the examples from earlier. The<br>
declarations include the determinism that ought to be inferred for the<br>
introduced predicates.<br>
<br>
Here is length/2 again:<br>
<br>
length(Xs, N) :- for _ maps Xs, [I, I + 1] folds [0, N] do true.<br>
<br>
In the (in, out) mode, this translates to:<br>
<br>
length(Xs, N) :- aux_1(Xs, 0, N).<br>
<br>
:- pred aux_1(list(T)::in, int::in, int::out) is det.<br>
<br>
aux_1(_M1, _Fa1, _Fb1) :-<br>
( if<br>
_M1 = []<br>
then<br>
_Fa1 = _Fb1<br>
else if<br>
_M1 = [_ | _Mr1]<br>
then<br>
_Fa1 = I,<br>
true,<br>
aux_1(_Mr1, I + 1, _Fb1)<br>
else<br>
error("length mismatch in inductive goal")<br>
).<br>
<br>
Here is map_corresponding3_foldl/7 again:<br>
<br>
map_corresponding3_foldl(P, As, Bs, Cs, Ds, !S) :-<br>
for A maps As, B maps Bs, C maps Cs, D maps Ds, [!S]<br>
mismatch throw("list.map_corresponding3_foldl: length mismatch")<br>
do P(A, B, C, D, !S).<br>
<br>
The mode of the sequences is (in, in, in, out, out), so we generate this:<br>
<br>
:- pred aux_2(<br>
pred(A, B, C, D, S, S)::in(pred(in, in, in, out, in, out) is det),<br>
list(A)::in, list(B)::in, list(C)::in, list(D)::out,<br>
S::in, S::out) is det.<br>
<br>
aux_2(P, _M1, _M2, _M3, _M4, _Fa1, _Fb1) :-<br>
( if<br>
_M1 = [], _M2 = [], _M3 = []<br>
then<br>
_M4 = [],<br>
_Fa1 = _Fb1<br>
else if<br>
_M1 = [A | _Mr1], _M2 = [B | _Mr2], _M3 = [C | _Mr3]<br>
then<br>
_M4 = [D | _Mr4],<br>
_Fa1 = _S_0,<br>
P(A, B, C, D, _S_0, _S_1),<br>
aux_2(P, _Mr1, _Mr2, _Mr3, _Mr4, _S_1, _Fb1)<br>
else<br>
throw("list.map_corresponding3_foldl: length mismatch")<br>
).<br>
<br>
If the mode of the sequences was (in, out, out, out, in, out), and<br>
without a mismatch clause, then we would instead generate code like<br>
map3_foldl:<br>
<br>
:- pred aux_3(<br>
pred(A, B, C, D, S, S)::in(pred(in, out, out, out, in, out) is det),<br>
list(A)::in, list(B)::out, list(C)::out, list(D)::out,<br>
S::in, S::out) is det.<br>
<br>
aux_3(P, _M1, _M2, _M3, _M4, _Fa1, _Fb1) :-<br>
( if<br>
_M1 = []<br>
then<br>
_M2 = [], _M3 = [], _M4 = [],<br>
_Fa1 = _Fb1<br>
else if<br>
_M1 = [A | _Mr1],<br>
then<br>
_M2 = [B | _Mr2], _M3 = [C | _Mr3], _M4 = [D | _Mr4],<br>
_Fa1 = _S_0,<br>
P(A, B, C, D, _S_0, _S_1),<br>
aux_3(P, _Mr1, _Mr2, _Mr3, _Mr4, _S_1, _Fb1)<br>
else<br>
error("length mismatch in inductive goal")<br>
).<br>
<br>
Here is take/3 again:<br>
<br>
take(N, List, Start) :-<br>
for<br>
[I, I + 1] folds [0, N],<br>
[[X | Xs0], Xs0] folds [List, _], % can fail<br>
X maps Start<br>
do true.<br>
<br>
We would generate:<br>
<br>
:- pred aux_4(list(T)::out, int::in, int::in, list(T)::in,<br>
list(T)::out) is semidet.<br>
<br>
aux_4(_M1, _Fa1, _Fb1, _Fa2, _Fb2) :-<br>
( if<br>
_Fa1 = _Fb1<br>
then<br>
_M1 = [],<br>
_Fa2 = _Fb2<br>
else if<br>
_Fa1 = I<br>
then<br>
_M1 = [X | _Mr1],<br>
_Fa2 = [X | Xs0],<br>
true,<br>
aux_4(_Mr1, I + 1, _Fb1, Xs0, _Fb2)<br>
else<br>
error("length mismatch in inductive goal")<br>
).<br>
<br>
Note that the unification of _Fa2 and [X | Xs0] can fail, making the<br>
whole predicate semidet. This occurs when there are not enough<br>
elements in List for the given value of N.<br>
<br>
Here's the goal to generate all lists of zeroes and ones:<br>
<br>
for X maps Xs do ( X = 0 ; X = 1 )<br>
<br>
We would generate:<br>
<br>
:- pred aux_5(list(int)::out) is multi.<br>
<br>
aux_5(_M1) :-<br>
(<br>
_M1 = []<br>
;<br>
_M1 = [X | _Mr1],<br>
( X = 0 ; X = 1 ),<br>
aux_5(_Mr1)<br>
).<br>
_______________________________________________<br>
developers mailing list<br>
<a href="mailto:developers@lists.mercurylang.org">developers@lists.mercurylang.org</a><br>
<a href="http://lists.mercurylang.org/listinfo/developers" target="_blank">http://lists.mercurylang.org/listinfo/developers</a><br>
</blockquote></div>