Open vasslitvinov opened 2 years ago
During today's domain module re-review, we generally liked this proposal and upheld the goal of having domains and ranges behave the same way whenever applicable.
We also wanted to entertain this alternative proposal: do all the same, except replace "allow only the set interpretation with irregular domains" with "allow only the translation interpretation with all domains".
Specifically:
dom + idx
dom - idx
idx + dom
will produce dom
translated by idx
dom + dom
dom - dom
idx - dom
will produce a compilation errorThe key benefit is that this gives us something very close to promotion, except here we get a compact representation. Promotion is what we'd get without treating these cases specially, and also what we get for these operations on arrays. So this alternative proposal offers something close to consistency.
Open questions with this alternative proposal:
for dom + idx
where dom
is an irregular domain, should the outcome be:
how to treat |
&
^
?
for dom + idx where dom is an irregular domain, should the outcome be:
IMO compilation error, since otherwise it would mean "translate" and "translate" doesn't make sense for irregular.
how to treat | & ^ ?
IMO leave them to be promoted unless we decide to make errors for *
/
%
on domains
Reporting on my experimentation.
+
-
|
&
^
*
/
%
op=
I ran a full paratest on each of the following changes:
op=
for the above operators a compiler error if LHS is a domain or a range: https://github.com/vasslitvinov/chapel/compare/f642747985...df4649c85f+
-
|
&
^
in the non-op=
experimentThe only failed test that is intended as user-like code is release/examples/primers/associative
, plus its dyno
counterpart. It goes like this:
// test/release/examples/primers/associative.chpl
var primeDom = {2, 3, 5, 7, 11, 13, 17}; // some prime numbers
var fibDom = {0, 1, 1, 2, 3, 5, 8, 13}; // part of the Fibonacci sequence
var primeAndFib = primeDom & fibDom;
writeln("Some primes in the Fibonacci sequence: ", primeAndFib);
writeln("Some primes not in the Fibonacci sequence: ", primeDom - primeAndFib);
var Names: domain(string);
Names += "some name";
Name += "another name";
.....
Name -= "an existing name";
.....
var Women = {"Alice", "Dana", "Ellen"};
var Men = Names - Women;
assert( (Men | Women) == Names );
All in all, 33 tests failed. A lot of them failed because they invoked an op=
which is implemented via the corresponding op
that now reports an error. A handful of the failures are not significant for this discussion, like due to a different number of resolution candidates in visibility/
tests.
+=
-=
|=
&=
^=
in the op=
experimentOverall: ~400 tests use at least one of these on domains/ranges. Of these:
+=
to add an element to an associative domain+=
to add an element to a sparse domain+=
to add an element-=
to remove an elementfrom an associative domainDisclaimer: my experiment was simplistic as it counted only a single op=
per (test,compopt) and included "uninteresting" tests that verify existing compiler errors for these op=
. So the counts here are approximate.
Let us focus on "user-like" codes, as opposed to code written for testing purposes.
Of these 69 tests:
(assoc dom) += (an index)
(sparse dom) += (an index)
(assoc dom) -= (an index)
(CSDom) += (DR dom)
The predominant use is to add an element to an irregular domain using +=
.
I suggest that the key choice on this issue is whether we want +
to mean "translate" aka shift or we want +
to mean a set operation.
I suggest that set operations on irregular domains such as union or difference should not be such a common pattern as to deserve the convenience of the operator syntax.
Using +=
to add an element is handy, despite my previous bullet. However, replacing it with .add()
has comparable benefits:
+
to mean "translate"We are considering these unstable for 2.0. I removed the 2.0 label.
This is a summary and generalization of #17101. It proposes a user view of these operations that is simple and uniform. related: * / % in #19264
Summary
There are several ways that each of these operations can be interpreted. We favor keeping one of these interpretation -- set operations on irregular domains -- and disabling all the other interpretations with compiler errors.
This gives us the flexibility to respond to future user requests to enable other interpretation(s) -- while remaining backwards compatible.
Also, the implementation strategy proposed here allows user code to define and use other interpretations as desired.
Possible interpretations
These operations, when applied to one or two domains (similarly for range(s) and combinations), could be interpreted as:
+
and-
on a domain and a number: translating the index set+
and-
on two rectangular domains: take the bounding box ex.{1..5} + {3..7}
→{1..7}
// how about{1..2} + {5..7}
?dom1 | dom2
is a promotion this interpretation is used forarray op array
Discussion
For
+
and-
there can be a confusion whether they mean a set operation, which is valid only on irregular domains, or translation of the index set, which we have done only for rectangular domains and ranges. We would like to eliminate such confusion by allowing the former and disallowing the latter. See, for example, https://github.com/chapel-lang/chapel/issues/17101#issuecomment-775155915 and https://github.com/chapel-lang/chapel/issues/17101#issuecomment-859156659 .We also do not want to leave room for confusion between the set interpretation and promotion https://github.com/chapel-lang/chapel/issues/17101#issuecomment-776708194 and dislike their promoted interpretation https://github.com/chapel-lang/chapel/issues/17101#issuecomment-859154057 .
As an anecdote,
(a two-dimensional rectangular domain) + (an integer)
resolves to a promoted+
on a 2-tuple and integer, producing a stream of shifted indices. While this is a valid interpretation, it is more likely not what the user intended.As another anecdote,
(an associative domain of integers) + (a range)
produces a stream of newly-created associative domains that result by adding each index of the range to the original domain. This also is a valid yet unlikely interpretation. This proposal makes this expression a compilation error.This leads us to disallowing these operations in all cases except as set operations on irregular domains.
Notably, this takes away the closed-form algebra of transformations, whereby
(a range) + (an integer)
would still be still a range. This sounds acceptable to us, given that the closed-form algebra can be achieved using named functions liketranslate
. // Its specifics are discussed in #17127 .Implementation proposal
This proposal is to provide the following three overloads for each operation:
Each overload contains param conditionals that check the types of
dom
andother
and invoke the desired implementation in each case.Cf. currently each operation contains overloads catering to specific implementations, ex. one overload for (rectangular domain) + (index), another for (irregular domain) + (index), etc.
The primary benefit is that we can easily issue easy-to-understand "not allowed" messages in all the cases that are not allowed.
The secondary benefit is that it is easier for users to overload more specific cases. For example, if I want to make my stencil computations look pretty like in https://github.com/chapel-lang/chapel/issues/17101#issuecomment-774342834 :
I can define
operator +(in dom: domain, dir: 2*int) return dom.translate(dir);
perhaps with a where-clause. The resolution will prefer it in my code over the predefined overloads.One big downside of this implementation style in general is that I do not get a promotion of an allowed case. Instead resolution will match my wannabe-promoted actual against the fully-generic formal and likely issue the error "this case is not allowed". However, an error message is exactly what we want under this proposal, given that we are disallowing promotion interpretations.
Clarification
This proposal does not outlaw the underlying operations or promotion. It simply requires the user to be explicit about what they mean: