chapel-lang / chapel

a Productive Parallel Programming Language
https://chapel-lang.org
Other
1.77k stars 417 forks source link

[Feature Request]: Add operator `*=` and friends to Chapel tuples #25530

Open alvaradoo opened 1 month ago

alvaradoo commented 1 month ago

Summary of Feature

Description: Currently, one can rewrite an assignment of multiple variables as tuple packing. This feature request is to add the ability to not only do this with assignment, but also with the *= operator and others. These would include +=, -=, and /=. I provide more details and ideas in the code sample below.

Is this issue currently blocking your progress? Nope!

Code Sample

var A = [0,1,2,3];
var B = [1,2,3,4];
var aArr = [-1,-2,-3,-4];
var bArr = [-5,-6,-7,-8];
var aInt = 2;
var bInt = 3;

(A,B) = (aArr,bArr);
writeln(A); // prints: -1 -2 -3 -4
writeln(B); // prints: -5 -6 -7 -8

(A,B) = (aInt,bInt);
writeln(A); // prints: 2 2 2 2
writeln(B); // prints: 3 3 3 3

const cArr = [10, 100, 1000, 10000];
const cInt = 1000;

The main goal is to have operations like these below possible.

(A,B) *= (aArr, bArr); 
(A,B) *= (aInt, bInt);

However, should we also consider allowing for operations when the LHS is a tuple and the RHS is just some variable? If the operations of A = cArr and B = cArr are legal, then maybe we should consider allowing the operations below for nicer code. Further, we do allow of promotion for array operations such as aArr *= 2, so this feels like it falls into the same vein, and should be alright to be supported.

(A,B) = cArr;
(A,B) = cInt;
(A,B) *= cArr;
(A,B) *= cInt;
Iainmon commented 1 month ago

Here is a template implementation:

operator *=(ref a: ta, b: tb) where isTupleType(ta) && isTupleType(tb) && a.size == b.size {
    for param i in 0..<a.size do 
        a(i) *= b(i);
}
Iainmon commented 1 month ago

Here is a template implementation:

operator *=(ref a: ta, b: tb) where isTupleType(ta) && isTupleType(tb) && a.size == b.size {
    for param i in 0..<a.size do 
        a(i) *= b(i);
}

Would it be better to have this loop run in parallel via a forall loop? Maybe forall where a.size > 10 and foreach otherwise? But tuples are usually small and don't hold big data...

mppf commented 1 month ago

I'd expect you'd need quite a few more tuple elements for forall to offer any benefit. Anyway for param will allow for tuples of non-homogenous type. For example (int, uint). I think that might be unintentional here -- I would expect that if we want to allow *= on tuples, it should be for only tuples with the same element type throughout (which we call "star tuples" or "homogenous tuples").

Even so, I'd probably focus on using foreach or for param to enable vectorization of these operations. I'd probably avoid using forall since it's unlikely that the operation will take longer than it would to create a task.

alvaradoo commented 1 month ago

I am not sure if agree with *= being only allowed on homogeneous tuples. As a user, I would assume that if I can do something like below, and am allowed to use *= with promotion on tuples, I should be able to rewrite it as (a,b) *= c, even though it is a heterogeneous tuple. This is because to me, I often use tuples as a nice, compact way to store many variables when I need to return multiple variables, assign values between multiple variables, etc. Maybe we should defer to accepting *= (and friends) if the types of the variables within tuples we are applying *= to allow it.

var a:real = 2.0;
var b:int = 3;
const c:int = 2;
a *= c;
b *= c;
// might want to be able to do (a,b) *=c
mppf commented 1 month ago

@alvaradoo - the reason I was saying it shouldn't be allowed for non-homogenous tuples is that we don't allow tuple-scalar operations in such cases today.

var intIntTup = (1, 2);
var x = intIntTup * 5; // ok
writeln(x);

var intRealTup = (1, 2.0);
var y = intRealTup * 5; // fails to resolve
writeln(y);

We might have limited tuples in this way to prevent code from getting too confusing. I haven't tracked down the justification.

bradcray commented 1 month ago

We might have limited tuples in this way to prevent code from getting too confusing. I haven't tracked down the justification.

I think that's the case. IIRC, we originally supported promotion across homog. and het. tuples; then got nervous about the het. case and removed all promotion; then realized how much we missed the homog. case and re-instated it. I think we discussed potentially supporting the het. case through a utility module (i.e., not by default), but I don't think anybody ever missed it enough to take that work on.