3 unstable releases
0.2.1 | Jun 23, 2023 |
---|---|
0.2.0 | Jun 22, 2023 |
0.1.0 | Jun 22, 2023 |
#8 in #conflict
31KB
388 lines
SUS impls
You can try out the algorithm by running the binary and giving it an input. Example:
cargo run "all(any(a, b, c), any(d, e, f))"
Output:
a b c d e f
S _ _ S _ _
S _ _ U S _
S _ _ U U S
U S _ S _ _
U S _ U S _
U S _ U U S
U U S S _ _
U U S U S _
U U S U U S
How it works
This rather long section explains how the algorithm works.
First, initial implementations are generated from the input. Then, possible logical input conflicts are rejected. Finally, type system conflicts are fixed.
Initial implementations
Number of initial implementations
The number of initial implementations n_init
can be calculated recursively:
- A field
a
requires one initial implementation:n_init(a) = 1
. n_init(any(arg_0, …, arg_n)) = n_init(arg_0) + … + n_init(arg_n)
(sum)n_init(all(arg_0, …, arg_n)) = n_init(arg_0) * … * n_init(arg_n)
(product)n_init(not(arg)) = n_init(arg)
Example:
n_init(all(any(a, b), any(c, d)))
= n_init(any(a, b)) * n_init(any(c, d))
= (n_init(a) + n_init(b)) * (n_init(c) + n_init(d))
= (1 + 1) * (1 + 1)
= 2 * 2
= 4
Setting initial implementations
First, an implementation matrix impls
represented as a vector of vectors of S
for "set", U
for "unset" or _
for "generic" is created.
The number of rows is n_init
.
The number of columns is the number of fields.
The matrix is initialized with _
at the beginning.
Example all(any(a, b), any(c, d))
with n_init = 4
:
a b c d
_ _ _ _
_ _ _ _
_ _ _ _
_ _ _ _
This matrix will be filled with a function set_init
that takes the following arguments:
impls
: The mutable matrix (or part of it).- A group (
all(…)
,any(…)
,not(…)
or just a field). invert
: Whether to setU
instead ofS
. Needed to supportnot
.
This function is implemented recursively and will be explained for all four possible cases in the next subsections.
Field
For a field a
, set_init(impls, a, invert)
sets the column a
for every row in impls
to U
if invert
is true
, otherwise to S
.
We have to set every row, not only the first one.
This will be clear in the example all(any(a, b), all(c, d))
when the case all
is explained.
Example a
:
a
S
Not
not
inverts the argument invert
.
set_init(impls, not(GROUP), invert)
= set_init(impls, GROUP, !invert)
Example not(a)
:
a
U
All
set_init(impls, all(arg_0, …, arg_n), invert)
is implemented in the following way:
- Call
set_init(impls, arg_0, invert)
. - Divide the rows of
impls
in sectionssection_i
withn_init(all(arg_1, …, arg_n))
rows (withoutarg_0
). - Call
set_init(section_i, all(arg_1, …, arg_n), invert)
for everyi
.
Example all(a, b)
:
a b
S S
The reason behind the sections section_i
will be clear in the next section when any
is nested inside all
.
Any
The naive way to implement set_init(impls, any(arg_0, …, arg_n), invert)
is the following:
- Divide the rows of
impls
into sectionssection_i
such that the first section is the firstn_init(arg_0)
rows, the second section is then_init(arg_1)
rows after the first section and so on. - Call
set_init(section_i, arg_i, arg_n)
for everyi
.
Example any(a, b)
:
a b
S _
_ S
But what if impls
has more rows than n_init(arg_0) + … + n_init(arg_n)
?
This can be the case if any
is nested inside of all
. In that case, the number of rows in impls
can be a multiple mult
of n_init(arg_0) + … + n_init(arg_n)
.
Therefore, we have to build the sections such that the first section is the first mult * n_init(arg_0)
rows and so on.
Example all(any(a, b), any(b, c))
:
a b c d
S _ S _
S _ _ S
_ S S _
_ S _ S
The first step in the example is set_init(impls, any(a, b), false)
:
a b c d
S _ _ _
S _ _ _
_ S _ _
_ S _ _
The second step set_init(impls[..2], any(c, d), false)
:
a b c d
S _ S _
S _ _ S
_ S _ _
_ S _ _
The last step set_init(impls[2..], any(c, d), false)
:
a b c d
S _ S _
S _ _ S
_ S S _
_ S _ S
Logical input conflicts
The algorithm searches for logical input conflicts in the matrix with initial implementations.
I call these conflicts logical to distinguish them from the type system conflicts in the next sections.
Two initial implementations have no logical conflicts in the following two cases:
- The first implementation has a generic where the other has a non-generic AND the first has a non-generic where the other has a generic.
- The first implementation has
S
where the other hasU
OR the first hasU
where the other hasS
.
The first case makes sure that any(all(a_0, …, a_n), all(b_0, …, b_m))
is logically non-conflicting if there is
at least one i
where a_i != b_j
for all j
AND
at least one i
where b_i != b_j
for all j
.
One of the two conditions are not enough!
Example: any(all(a, b), all(a))
.
Here, we have one condition b != a
, but it is not enough.
We need something like any(all(a, b), all(a, c))
where we also have c != b
.
The second case makes sure that any(all(a_0, …, a_n), all(b_0, …, b_m))
is logically non-conflicting
if there is at least one i
and j
where b_i = not(a_j)
.
An example with a logical conflict related to the first case: any(all(a, b), a)
a b
S S
S _
An example without logical conflicts related to the first case: any(all(a, b), all(a, c))
a b c
S S _
S _ S
An example with a logical conflict related to the second case: any(all(a, b), all(a, b))
a b
S S
S S
An example without logical conflicts related to the second case: any(all(a, b), all(not(a), b))
a b
S S
U S
Although the last example can be simplified to only require b
, it has no logical conflicts.
The simplified matrix would be:
a b
_ S
This algorithm does not make such simplifications. The user is responsible for such an optimization.
Sorting
After the initial implementations are set, it is worth it to sort the implementations by the number of non-generics.
This can be explained using the following example any(all(a, b), c)
:
a b c
S S _
_ _ S
The solution of this matrix with initial implementations that fixes type system conflicts is the following:
a b c
S S _
U _ S
S U S
The details of this solution will be discussed in the next section.
If we start with the following matrix:
a b c
_ _ S
S S _
Then the solution includes only two instead of three implementations:
a b c
_ _ S
S S U
Since the algorithm to fix type system conflicts (see next section) works from top to bottom, this sorting makes sure that upper implementations have a lower potential of conflicts than lower implementations by having more generic fields.
Fixing type system conflicts
Steps motivation
In this section, the steps of the algorithm to fix type system conflicts are motivated to be presented in the next section.
After sorting initial implementations without logical conflicts, we have to fix conflicts for the type system.
Let's say that we have any(a, b)
.
Then we have the following two implementations:
a b
S _
_ S
What would happen when both a
and b
are S
?
Which implementation should be used then?
This can not be determined and Rust throws a conflict error at compile time.
One solution is the following:
a b
S U
U S
S S
We did specify that the first two implementations can be used when only a
or b
is S
.
Then we added a third implementation for the case that both a
and b
are S
.
To remove the additional implementation, we could merge it with only one of the first two using a generic.
Either
a b
S U
_ S
or
a b
S _
U S
We want to develop an algorithm that fixes such issues from top to bottom. Therefore, we choose the second variant.
Let's take a look at another example and use our oberservation while solving the last one.
The example is any(a, b, c)
:
a b c
S _ _
_ S _
_ _ S
Let's add a U
below every S
as we did in the last example:
a b c
S _ _
U S _
_ U S
The first and last implementations are still conflicting!
We need to add another U
in the last implementation:
a b c
S _ _
U S _
U U S
I call this observed strategy "casting shadows" because it looks like S
casting a U
shadow to all implementations below it.
What if there is an S
somewhere below an S
?
For example any(all(a, b), all(a, c), all(b, c))
:
a b c
S S _
S _ S
_ S S
We don't overwrite any S
. We cast the shadow ignoring any S
on the way:
a b c
S S _
S U S
U S S
Now, let's take a look at the example any(all(a, b), all(c, d))
:
a b c d
S S _ _
_ _ S S
If we just cast a shadow like in the examples above, we get the following:
a b c d
S S _ _
U U S S
The conflicts are fixed.
But we don't have any implementation for the case where c
, d
and (a
XOR b
) are S
although the condition of having all(c, d)
is fulfilled.
The solution for this issue is motivated by any(a, b, c)
. We cast a diagonal shadow of U
with S
under it adding additional implementations as required:
a b c d
S S _ _
U _ S S
-------
S U S S
We separate initial implementations from additional ones with ---
which is relevant for later.
Another example with a longer diagonal shadow any(all(a, b, c), all(d, e, f))
:
a b c d e f
S S S _ _ _
U _ _ S S S
-----------
S U _ S S S
S S U S S S
If we find a U
in a lower implementation while we are trying to cast a U
shadow,
we don't change anything in that implementation.
There is no conflict since there is already a U
in the column where the higher implementation has S
.
Therefore we can move to the next lower implementation.
Example any(all(a, b), all(a, c), all(d, e))
:
a b c d e
S S _ _ _
S _ S _ _
_ _ _ S S
First, we cast the shadow of the first implementation:
a b c d e
S S _ _ _
S U S _ _
U _ _ S S
---------
S U _ S S
Now, we cast the shadow of the second implementation:
a b c d e
S S _ _ _
S U S _ _
U _ _ S S
---------
S U U S S
We didn't write U
in c
of the third implementation
because there is U
under one S
of the second implementation.
Dealing with not
not
introduces a new class of problems.
An example is any(all(a, c), all(not(a), b), all(b, c))
:
a b c
S _ S
U S _
_ S S
The algorithm so far does only one step by adding one U
to the third implementation:
a b c
S _ S
U S _
U S S
But now, the second and third implementations are conflicting!
The solution is to remove the less generic implementation which is the third one:
a b c
S _ S
U S _
This means that we have to fix possible conflicting implementations in the mutated initial implementations when we have a not
.
This requires looping through the mutated initial implementations, searching for conflicts, and only keeping the more generic implementations.
Cleaning up additional implementations
Sometimes, the algorithm so far generates additional implementations that are conflicting with the (mutated) initial implementations.
Example all(any(a, b), any(c, d))
:
a b c d
S _ S _
S _ _ S
_ S S _
_ S _ S
The result is:
a b c d
S _ S _
S _ U S
U S S _
U S U S
-------
S U U S
But the additional implementation is conflicting with the second implementation and has to be removed!
Therefore, the algorithm has to clean up additional implementations at the end.
Steps
- For every implementation, find the indices of all
S
naming the vectorfocus_indices
. - Iterate through each lower implementation and find the indices of all
_
that overlap withfocus_indices
naming the vectorgeneric_indices
. If you find at least oneU
that overlaps withfocus_indices
, skip this lower implementation. - Set the first
U
as the first index ingeneric_indices
by mutating the lower implementation. - Set the rest of the diagonal
U
shadow withS
under it ingeneric_indices
while adding additional implementations ifgeneric_indices
has more than one index. - If a
not
exists in the input:- Repeat steps 1-4 while swapping every mention of
S
withU
and every mention ofU
withS
. This step is needed to fixU
conflicts. In step 1, find only the indices ofU
from the original unmutated initial implementations. This means that allU
set in steps 3-4 don't count. - Remove possible less generic implementations from the mutated initial implementations.
- Repeat steps 1-4 while swapping every mention of
- Remove additional implementations that conflict with the mutated initial implementations.
Dependencies
~260–690KB
~17K SLoC