Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

do we want variable-arity operators? #451

Closed
zygoloid opened this issue Apr 12, 2021 · 18 comments
Closed

do we want variable-arity operators? #451

zygoloid opened this issue Apr 12, 2021 · 18 comments
Labels
leads question A question for the leads team

Comments

@zygoloid
Copy link
Contributor

zygoloid commented Apr 12, 2021

In (at least) three cases, we may wish to consider treating traditionally infix binary operators as variable-arity operators. The cases are:

  1. Comparisons
if (a <= b <= c) {}
if (a == b == c) {}
if (a <= b == c <= d) {}

Options:

a) We pick a precedence and associativity rule, and compare the Bool result of one operator with the operand of another operator. [This is the traditional / C++ choice.]
b) We treat these operators as being non-associative / having incomparable precedence, and reject these examples unless parentheses are added. [This is the current approach in #168.]
c) We treat comparison operators as being variable-arity (with the same operator), so the above are desugared into something like:

if (LessEqualCompare((a, b, c))) {} // call method of suitable interface passing a 3-tuple
if (EqualEqualCompare((a, b, c))) {} // call method of suitable interface passing a 3-tuple

The third if statement would be ill-formed.
d) We treat comparison operators as being variable-arity, and desugar them into chained binary operators, so the above are desugared into something like:

if (a <= b and b <= c) {}
if (a == b and b == c) {}
if (a <= b and b == c and c <= d) {}

... except that the operands are each evaluated once before any comparisons are performed. [This is the Python choice.]

Note in particular that the variable-arity option is not the same as treating <= and friends as operators with a funny return type with a custom <= operator. For example, these are not equivalent:

if (a == b == c) {} // are a, b, and c equal?
if ((a == b) == c) {} // is the result of comparing a and b the boolean value c?
  1. Commas for tuple formation

We can consider allowing an unparenthesized comma expression to form a tuple, and thereby uniformly use parentheses only for grouping. For example:

var (Int, Int, Int) x;
x = 1, 2, 3; // OK, assuming ',' is higher precedence than '='

Then , would be a variable-arity operator that forms tuples of arity >= 2. () and (a,) would be syntactic special cases that require parentheses.

a) Don't do this; the parentheses are part of the tuple syntax. [This is the Haskell choice.]
b) Do this; the parentheses surrounding (a, b, c) are optional. [This is the Python choice.]

  1. Chained assignment
x = y = z;

There are multiple ways this could work:

a) This is two uses of a right-associative binary = operator. This code converts z to the type of y and stores to y, then [loads from y and] converts the result to the type of x and stores to x. [This is the traditional choice; C++ reloads y whereas C does not. This is also the current approach in #168.]
b) This is ill-formed. We don't support chained assignment.
c) This is one use of a variable-arity = operator. The value z is stored to both x and y. [This is the Python choice.]

@zygoloid
Copy link
Contributor Author

My current preferences (ordered most to least preferred):

1: d[same direction of comparison only] c b d[any direction] a
2: a b
3: c a b

For 1: I don't really want to support a < b >= c.
For 3: Assignment is the only right-associative operator at the moment, and (if we already have other variable-arity operators) treating it as variable-arity would allow us to remove that special case.

@josh11b
Copy link
Contributor

josh11b commented Apr 12, 2021

1: d[same direction of comparison only] b c d[any direction]
(I don't consider "a" an option.)

2: a b

3: b c a
(I consider "a" and "c" to be an almost tie.)

@geoffromer
Copy link
Contributor

Another possible use case for variable-arity operators is that it would let us support + for string concatenation, while avoiding the performance problems that arise from that syntax in C++. I don't think I'd support doing this, but it seems worth mentioning.

I don't feel like any of the variable-arity options discussed above offers a good cost/benefit tradeoff. 1d might come close, but it would still add some nontrivial complexity to the language, while addressing only a fairly specialized set of use cases.

@fowles
Copy link

fowles commented Apr 13, 2021

QQ about 1d

Given: a < b < c
Is c evaluated if a == b

@zygoloid
Copy link
Contributor Author

Is c evaluated if a == b

Happy to hear arguments either way. My intention was that it is -- I think that's the less surprising behavior, even though it results in unnecessary computations.

@tkoeppe
Copy link
Contributor

tkoeppe commented Apr 13, 2021

I find it unfortunate that a seemingly inviting and attractive syntax results in suboptimal performance. I would find it equally unfortunate if a short-circuiting "and" was implicit. So I think on balance I'd probably prefer to just make everything ill-formed, and make people spell out what they mean using parentheses.

@fowles
Copy link

fowles commented Apr 13, 2021

Is c evaluated if a == b

Happy to hear arguments either way. My intention was that it is -- I think that's the less surprising behavior, even though it results in unnecessary computations.

I would have assumed that it short circuits left to right as it can because that is what conditional expressions have trained me to expect.

@tkoeppe
Copy link
Contributor

tkoeppe commented Apr 13, 2021

Is c evaluated if a == b

Happy to hear arguments either way. My intention was that it is -- I think that's the less surprising behavior, even though it results in unnecessary computations.

I would have assumed that it short circuits left to right as it can because that is what conditional expressions have trained me to expect.

That said, I'm not sure how much anyone's intuition is trained on expecting short circuiting in a < b < c!

@chandlerc
Copy link
Contributor

Regardless of the rest of this, I have a strong preference for (2a) and (3b). I feel like tuples should be a language syntax feature, and not rely on operators. Similarly, I think assignment should be a language feature and we should not try to get into chained assignment.

I struggle with (1d) because it seems hard to generalize and invites short circuiting ambiguities as discussed here.

Fundamentally, I think if we want any variable arity operators we should make it a general facility and not try to desugar things.

So for me, the real question is around a general purpose variable-arity operator.

It would give us at least same-operator cases when handling comparisons, as well as lots of other use cases like streaming, concatenation, etc. It also makes the short circuiting and such very explicit and obvious (none). And the and operation (whether spelled as and or &&) becomes syntax and not an operator, and is thus in a good position to have control flow.

I'm mildly in favor of having this general facility and defining our binary operators in terms of variadics for this purpose as long as it doesn't slow us down.

@tkoeppe
Copy link
Contributor

tkoeppe commented Apr 24, 2021

Fundamentally, I think if we want any variable arity operators we should make it a general facility and not try to desugar things.

How would a general facility provide anything akin to a < b < c? That would essentially require a kind of "pairwise-fold" operation, woudln't it (binary-< between pairs of elements, and logical-and to accumulate).

@zygoloid
Copy link
Contributor Author

That said, I'm not sure how much anyone's intuition is trained on expecting short circuiting in a < b < c!

Looks like Python short-circuits evaluation in that case: c is only evaluated if a < b evaluates to True. But I'm not sure whether many practicing Python programmers know this, let alone rely on it!

@josh11b
Copy link
Contributor

josh11b commented Apr 27, 2021 via email

@tkoeppe
Copy link
Contributor

tkoeppe commented Apr 27, 2021

A slighty more tangential observation on maths and operators. Let me set the scene first. I was once convnicingly advised to write interval checks in "maths order", such as 0 <= i && i < N, to mimic the maths interval notation [0, N); "only compare in one direction". I've been finding that very helpful and readable.

Now, a similar argument can be made in other relational situations. Therefore, one might well be tempted to reorder comparison statements according to such mathematical tastes. If there is short-circuiting among relational (!) operators, then such seemingly innocent, style-only edits potentially change the meaning of code.

So, I think it's perfectly natural and "familiar" for logical operators to short-cicruit, but I find it rather novel and inventive for relational operators to do so. And it's not because it wouldn't be a good optimization/performance choice, but because I worry about requring readers to know about and preserve this!

Or, in other words, short-circuiting relational operators would need strong justification in light of the Goal of "Familiarity for experienced C++ programmers".

@zygoloid
Copy link
Contributor Author

I think we've reached consensus on some of the questions here.

Partial decision:
2a: parentheses are part of the required syntax for tuple formation; commas without parentheses do not suffice
3b: we initially do not support chained assignment

The question regarding chained comparisons is still open.

@github-actions
Copy link

github-actions bot commented Aug 9, 2021

We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time.
This issue is labeled inactive because the last activity was over 90 days ago.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Aug 9, 2021
@jonmeow jonmeow added the leads question A question for the leads team label Aug 10, 2022
@github-actions github-actions bot removed the inactive Issues and PRs which have been inactive for at least 90 days. label Aug 11, 2022
@github-actions
Copy link

We triage inactive PRs and issues in order to make it easier to find active work. If this issue should remain active or becomes active again, please comment or remove the inactive label. The long term label can also be added for issues which are expected to take time.
This issue is labeled inactive because the last activity was over 90 days ago.

@github-actions github-actions bot added the inactive Issues and PRs which have been inactive for at least 90 days. label Nov 11, 2022
@josh11b josh11b removed the inactive Issues and PRs which have been inactive for at least 90 days. label Nov 11, 2022
@josh11b
Copy link
Contributor

josh11b commented Nov 11, 2022

It seems we have in practice adopted 1B, for example in #702 . I recommend affirming that decision and closing this issue.

@chandlerc
Copy link
Contributor

Yep, leads have consensus on #451 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
leads question A question for the leads team
Projects
None yet
Development

No branches or pull requests

7 participants