• Rationales for result type of operations using operators like '+='

    From Janis Papanagnou@3:633/10 to All on Wed Nov 4 08:53:38 2026
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound" "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    i += 2 *= 3 -= 5 %= 11; // impossible in C and C++
    (((i += 2) *= 3) -= 5) %= 11; // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows
    i +:= 2 *:= 3 -:= 5 %*:= 11
    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Wed Nov 4 10:56:06 2026
    On 04/11/2026 08:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound" "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.


    I don't see a distinction here between compound assignment and simple assignment - the compound assignment operators follow the simple
    assignment operator for consistency, and because "x += y" is defined to
    mean "x = x + y". So I think that we can just talk about simple
    assignment (or at least, talk about it first).

    If you figure out (a), then (b) is the only possible choice. That is,
    if you write

    x = y = z;

    it could not be interpreted as

    (x = y) = z;

    unless "x = y" were an lvalue.

    For simple assignment, if "x = y = z" were left associative, and "x = y"
    were an lvalue, that would mean that instead of being

    y = z;
    x = y;

    it would now mean :

    x = y;
    x = z;

    which is clearly not a useful construct.

    (Personally, I'd rather that assignment had been a kind of statement
    rather than an expression - but if a language is going to make
    assignment an expression, it's better that it is a potentially useful one!)

    I think that all covers (b) - why assignment (simple and compound) is right-associative.

    Then there is (a) - why is the result of an assignment an rvalue, not an lvalue? And why is it an lvalue in C++ ?

    I don't know an answer to that, other than that I don't think there
    would be any benefit of making it an lvalue in C. (In C++, operators
    can be overloaded and may do other things - making the result an lvalue
    adds a little flexibility.)



    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows
    ˙ i +:= 2 *:= 3 -:= 5 %*:= 11
    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Kaz Kylheku@3:633/10 to All on Thu Nov 5 01:35:06 2026
    On 2026-11-04, Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound" "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    These properties make sense for the plain assignment operator,
    for supporting x = y = z = 0. If this expression denoted a single
    lvalue, that being x, then y and z would not receive any value,
    and would have to be initialized: first x would be assigned the value
    of y, then it would be assigned the value of z, and then zero.

    The operational assignment operators were given the same properties as
    plain assignment, without any thought of doing something usefully
    different.

    i += 2 *= 3 -= 5 %= 11; // impossible in C and C++

    But

    i += j += k *= 3

    /is/ possible. k gets multiplied by 3, then its new value is added
    to j, and the new value of j is added to i.

    (((i += 2) *= 3) -= 5) %= 11; // impossible in C, possible in C++

    Does that work for a basic type like int, and is it well-defined?

    Possibly, under modern C++, a "sequenced before" requirement rescues
    it, but I suspect that if we go back to C++98, it would have been
    undefined: i is modified multiple times without an intervening sequence
    point.


    --
    TXR Programming Language: http://nongnu.org/txr
    Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
    Mastodon: @Kazinator@mstdn.ca

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Waldek Hebisch@3:633/10 to All on Thu Nov 5 02:05:55 2026
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound" "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    i += 2 *= 3 -= 5 %= 11; // impossible in C and C++
    (((i += 2) *= 3) -= 5) %= 11; // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows
    i +:= 2 *:= 3 -:= 5 %*:= 11
    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    I think that one should look at the context. In the period when
    C was born there were several attempts to define higher level or
    portable assembly languages. If you look at say PL360 you will
    see that its operations were chosen to directly translate to
    360 operations. C is more abstract, but clearly constructs of
    C were motivated by ability to get efficient machine code.
    So various 'op=' operators were intended to correspond to
    operations of typical 2 address machine. But as Kerningham and
    Ritchie explanied there was second motivation: computing address
    of left hand side may be a complex comutation and 'op=' operators
    allow reuse of addressing computation. Now, let us look at
    cascade that you want. You have a seqence of operations on
    the same variable. If this variable in in machine register,
    then naive translation leads to seqence of 2 address machine
    instructions which is quite efficient. But writing this
    as a sequence of separate C instructions leads to slightly
    longer C code but identical machine code. If the variable
    is in memory, then naive translation implies repeated
    loads and stores which is clearly rather inefficient.
    It is much more efficient to keep temporary result in
    machine register and only assign final value and that is
    easily done by writing complex expression and assigning
    final result to variable. So, the example you gave is
    code that Ritchie did not want. OTOH when you are computing
    complex expression, then it may be quite useful to store
    values of subexpressions for future use. That is why it
    is natural to use value of assigment in future computations.

    I think that the above reasonably explains associativity.
    One could still make result to be lvalue, but by the above
    argument lvalue is of limited use and it would require some
    effort to efficiently support main use case, that is rvalue
    case and true lvalue use. I guess that here simplicty
    won: rvalue was needed and easy to implement, lvalue
    offered little benefits but extra effort.

    I do not know if this is substantial enough for you, but
    for me it looks quite consistent with other early decisions
    in C developement.

    --
    Waldek Hebisch

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Thu Nov 5 03:19:02 2026
    Thanks for your answer. (I'll keep all your explanations for context.)
    My comments below...

    On 2026-11-04 10:56, David Brown wrote:
    On 04/11/2026 08:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.


    I don't see a distinction here between compound assignment and simple assignment - the compound assignment operators follow the simple
    assignment operator for consistency, and because "x += y" is defined to
    mean "x = x + y".˙ So I think that we can just talk about simple
    assignment (or at least, talk about it first).

    Okay, I see where you're coming from. - Actually that reasoning is one
    of the couple rationales that the AI provided me as answer as well.

    Since I don't see the need to not have a distinction between = and op=
    I dismissed that. In consequence, as you say or imply, all you derive
    from that is thus also not convincing to me. - Let me expand on that...

    First allow that I see = and op= as different things. (Note that, e.g.,
    Algol 68 also differentiates here, so it's not a given that we handle
    them the same or semantically similar.) Allow having, at the design
    stage, the option to just define the most sensible characteristics to
    allow a consistent yet flexible use without "arbitrary" restrictions.

    Given that cascading is considered useful and also used in practice I'd
    like to consider that from another perspective; from use-cases.

    What use-cases are there that would make it sensible to allow (in "C")
    just rvalues, to prevent which misuses, or prevent what ambiguities, or
    which problems with compilers, or would introduce any other problem?

    What use-cases are there that would make it sensible to right-associate
    (as in "C" and C++) modifications of an lvalue.

    (Please keep in mind that there's yet no substantial need identified
    to consider the two syntactic elements similar.)

    I wouldn't think that a C-programmer would have difficulties to confuse semantics of = and op= (especially given that there's many a lot more
    confusing things in "C" as, e.g., the = and == choice). So "consistency"
    is as argument vague at best.

    I also understand what you wrote below that you'd preferred to not have
    the issues in the first place by having "var op= val" being a statement.

    (Since "C" is now actually returning a value the question remains why
    the two properties - that I formulated as questions - were designed as
    they are.)

    A design where you can cascade the op= as a pipeline processing appears
    quite appealing and natural to me (and I certainly used it; in C++ with
    the parenthesis necessary, and in Algol 68 I used it straightforward).

    So far on the general view "from outside".


    If you figure out (a), then (b) is the only possible choice.˙ That is,
    if you write

    ˙˙˙˙x = y = z;

    it could not be interpreted as

    ˙˙˙˙(x = y) = z;

    unless "x = y" were an lvalue.

    Assignment with right-associativity I consider natural and all is fine.


    For simple assignment, if "x = y = z" were left associative, and "x = y" were an lvalue, that would mean that instead of being

    ˙˙˙˙y = z;
    ˙˙˙˙x = y;

    it would now mean :

    ˙˙˙˙x = y;
    ˙˙˙˙x = z;

    which is clearly not a useful construct.

    Right, it's not a useful construct for assignments.


    (Personally, I'd rather that assignment had been a kind of statement
    rather than an expression - but if a language is going to make
    assignment an expression, it's better that it is a potentially useful one!)

    All fine for assignments.


    I think that all covers (b) - why assignment (simple and compound) is right-associative.

    And here, as mentioned already, I see the unnecessary (and design-wise artificial) equalization of the two distinct language atoms, = and op=.


    Then there is (a) - why is the result of an assignment an rvalue, not an lvalue?˙ And why is it an lvalue in C++ ?

    I don't know an answer to that, other than that I don't think there
    would be any benefit of making it an lvalue in C.˙ (In C++, operators
    can be overloaded and may do other things - making the result an lvalue
    adds a little flexibility.)

    Yes, that's what I also think; it adds flexibility. And avoids an IMO unnecessary restriction; if there's any side-effect that matters in "C"
    I've yet to see one or heard from one. That's why I was asking.

    Janis

    PS: It might help understanding why it appears natural to me, consider
    its semantics "operate [sequentially] on a variable in an expression";
    here an example with the two possible Algol 68 syntaxes

    number TIMESAB base PLUSAB digit

    number *:= base +:= digit

    which in "C" would be

    number *= base;
    number += digit;

    or

    number = number * base + digit

    and in C++ either one of the "C" forms, or the parenthesized form

    (number *= base) += digit

    So, in C/C++, two separate statements, variable duplication, spurious parenthesis. All adding to the often mentioned "cognitive load".


    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows
    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11
    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?




    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Thu Nov 5 03:36:16 2026
    On 2026-11-05 02:35, Kaz Kylheku wrote:
    On 2026-11-04, Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    These properties make sense for the plain assignment operator,

    Yes.

    for supporting x = y = z = 0. If this expression denoted a single
    lvalue, that being x, then y and z would not receive any value,
    and would have to be initialized: first x would be assigned the value
    of y, then it would be assigned the value of z, and then zero.

    The operational assignment operators were given the same properties as
    plain assignment, without any thought of doing something usefully
    different.

    I suspected that.


    i += 2 *= 3 -= 5 %= 11; // impossible in C and C++

    But

    i += j += k *= 3

    /is/ possible. k gets multiplied by 3, then its new value is added
    to j, and the new value of j is added to i.

    I see two problems; thinking an expression "reverse", and not being
    able to use integer literals (instead of variables). - In practice I'd
    then prefer the other workaround, like decomposition of the expression
    in separate statements.

    With parentheses we at least see that the first part of the expression
    returns an lvalue (and can potentially be used at the outer stages of
    the expression).


    (((i += 2) *= 3) -= 5) %= 11; // impossible in C, possible in C++

    Does that work for a basic type like int, and is it well-defined?

    Erm.. - isn't it? - I certainly have thought it is. And I see no reason
    for any surprise here. Do you?


    Possibly, under modern C++, a "sequenced before" requirement rescues
    it, but I suspect that if we go back to C++98, it would have been
    undefined: i is modified multiple times without an intervening sequence point.

    Hmm.. - I'm sure I used that pattern during the early 1990's. - Are you
    saying I was just lucky back then?

    Janis


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Thu Nov 5 03:58:21 2026
    On 2026-11-05 03:05, Waldek Hebisch wrote:
    Janis Papanagnou <janis_papanagnou+ng@hotmail.com> wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    i += 2 *= 3 -= 5 %= 11; // impossible in C and C++
    (((i += 2) *= 3) -= 5) %= 11; // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows
    i +:= 2 *:= 3 -:= 5 %*:= 11
    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    I think that one should look at the context. In the period when
    C was born there were several attempts to define higher level or
    portable assembly languages. If you look at say PL360 you will
    see that its operations were chosen to directly translate to
    360 operations. C is more abstract, but clearly constructs of
    C were motivated by ability to get efficient machine code.
    So various 'op=' operators were intended to correspond to
    operations of typical 2 address machine. But as Kerningham and
    Ritchie explanied there was second motivation: computing address
    of left hand side may be a complex comutation and 'op=' operators
    allow reuse of addressing computation. Now, let us look at
    cascade that you want. You have a seqence of operations on
    the same variable. If this variable in in machine register,
    then naive translation leads to seqence of 2 address machine
    instructions which is quite efficient. But writing this
    as a sequence of separate C instructions leads to slightly
    longer C code but identical machine code. If the variable
    is in memory, then naive translation implies repeated
    loads and stores which is clearly rather inefficient.
    It is much more efficient to keep temporary result in
    machine register and only assign final value and that is
    easily done by writing complex expression and assigning
    final result to variable. So, the example you gave is
    code that Ritchie did not want. OTOH when you are computing
    complex expression, then it may be quite useful to store
    values of subexpressions for future use. That is why it
    is natural to use value of assigment in future computations.

    I think that the above reasonably explains associativity.
    One could still make result to be lvalue, but by the above
    argument lvalue is of limited use and it would require some
    effort to efficiently support main use case, that is rvalue
    case and true lvalue use. I guess that here simplicty
    won: rvalue was needed and easy to implement, lvalue
    offered little benefits but extra effort.

    I do not know if this is substantial enough for you, but
    for me it looks quite consistent with other early decisions
    in C developement.

    Don't worry, all (non-AI) views are welcome. Thank you.

    Actually I also thought about mappings onto 2-address machine
    instructions, but I didn't see any problems with that. Say,

    a *= b += c; // with left-associativity; not "C"!

    would have been transformed to something like

    MULT a, b
    ADD a, c

    or even with 1-address instructions and an ACC register like

    LOAD a
    MULT b
    ADD c
    STORE a

    But anyway, I'd have also thought that mapping such simple
    expressions onto (any) assembly code would be trivial. (And
    I'm not even starting to talk about typical optimizations;
    which might not have been that common in K&R times.)

    Thanks again for your thoughts.

    Janis


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Thu Nov 5 11:33:11 2026
    On 05/11/2026 03:19, Janis Papanagnou wrote:
    Thanks for your answer. (I'll keep all your explanations for context.)
    My comments below...

    On 2026-11-04 10:56, David Brown wrote:
    On 04/11/2026 08:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.


    I don't see a distinction here between compound assignment and simple
    assignment - the compound assignment operators follow the simple
    assignment operator for consistency, and because "x += y" is defined
    to mean "x = x + y".˙ So I think that we can just talk about simple
    assignment (or at least, talk about it first).

    Okay, I see where you're coming from. - Actually that reasoning is one
    of the couple rationales that the AI provided me as answer as well.

    Since I don't see the need to not have a distinction between = and op=
    I dismissed that. In consequence, as you say or imply, all you derive
    from that is thus also not convincing to me. - Let me expand on that...

    I can agree that op= does not /need/ to be consistent with simple
    assignment for associativity and rvalue/lvalue results, but I think a
    language designer would need very strong reasons before allowing them to
    be inconsistent.


    First allow that I see = and op= as different things. (Note that, e.g.,
    Algol 68 also differentiates here, so it's not a given that we handle
    them the same or semantically similar.)

    Okay. I am quite happy to accept that and see where it leads us. (Note
    that I have almost no knowledge of Algol 68, if that is relevant.)

    Allow having, at the design
    stage, the option to just define the most sensible characteristics to
    allow a consistent yet flexible use without "arbitrary" restrictions.


    Okay.

    Given that cascading is considered useful and also used in practice I'd
    like to consider that from another perspective; from use-cases.

    Cascading is also considered unclear and questionable style, with poorly defined semantics in special cases (such as volatile accesses) or if
    there are other side effects involved.

    Different people will have different opinions on what should be allowed
    or disallowed in a language - and when they are willing to disallow some useful cases to ensure dangerous cases are compile-time errors, or when
    they are willing to allow some dangerous cases to ensure that useful
    cases are allowed.

    However the balance is picked for a language, it is inevitable that some potentially useful cases will be disallowed in order to reduce the risk
    of subtle errors in code.


    What use-cases are there that would make it sensible to allow (in "C")
    just rvalues, to prevent which misuses, or prevent what ambiguities, or
    which problems with compilers, or would introduce any other problem?

    Perhaps my imagination is lacking.

    For simple assignment, I can't think of any. "(a = b) = c;", if it were allowed in C, would mean the same as "a = b; a = c;" except that "a" is
    only evaluated once. (Imagine "a" was "xs[i++]" to see why that
    matters.) The only reason to write "a = b; a = c;" would be for the side-effects, such as if "b" and "c" are function calls or volatile
    variables. And I cannot see any circumstances where "(a = b) = c;"
    would be seen as clearer than "b; a = c;" or - if you want it as a
    single expression - "b, a = c;".

    For compound assignment, "(a += b) += c;" would mean (ignoring multiple evaluations of "a") :

    a = a + b;
    a = a + c;

    The only circumstances I can think of where "(a += b) += c" would be preferable to "a += (b + c)", is if the calculation order affected
    signed integer overflow or rounding in floating point. And then I think
    the code would be far better written as "a = (a + b) + c;".


    What use-cases are there that would make it sensible to right-associate
    (as in "C" and C++) modifications of an lvalue.

    In C++, functions can return references that are lvalues. Operators can
    be overloaded for user types, as functions. It is therefore consistent
    in the language that the result of assignment operators is an lvalue.
    But I don't see use-cases for lvalue results of assignment operators on standard types.


    (Please keep in mind that there's yet no substantial need identified
    to consider the two syntactic elements similar.)

    I wouldn't think that a C-programmer would have difficulties to confuse semantics of = and op= (especially given that there's many a lot more confusing things in "C" as, e.g., the = and == choice). So "consistency"
    is as argument vague at best.

    "C is so confusing that it doesn't matter if we make it worse" does not
    carry much weight with me. Again - I think it would be fine to have an inconsistency between simple and compound assignment, but only if there
    was a very strong reason /for/ the inconsistency. So far, you have not
    given any reason that I can see.


    I also understand what you wrote below that you'd preferred to not have
    the issues in the first place by having "var op= val" being a statement.

    (Since "C" is now actually returning a value the question remains why
    the two properties - that I formulated as questions - were designed as
    they are.)

    A design where you can cascade the op= as a pipeline processing appears
    quite appealing and natural to me (and I certainly used it; in C++ with
    the parenthesis necessary, and in Algol 68 I used it straightforward).

    Could you give some examples of how you use it in C++? That would, I
    think, make it a lot clearer to me why you think this is a good idea.


    So far on the general view "from outside".


    If you figure out (a), then (b) is the only possible choice.˙ That is,
    if you write

    ˙˙˙˙˙x = y = z;

    it could not be interpreted as

    ˙˙˙˙˙(x = y) = z;

    unless "x = y" were an lvalue.

    Assignment with right-associativity I consider natural and all is fine.


    For simple assignment, if "x = y = z" were left associative, and "x =
    y" were an lvalue, that would mean that instead of being

    ˙˙˙˙˙y = z;
    ˙˙˙˙˙x = y;

    it would now mean :

    ˙˙˙˙˙x = y;
    ˙˙˙˙˙x = z;

    which is clearly not a useful construct.

    Right, it's not a useful construct for assignments.


    (Personally, I'd rather that assignment had been a kind of statement
    rather than an expression - but if a language is going to make
    assignment an expression, it's better that it is a potentially useful
    one!)

    All fine for assignments.


    I think that all covers (b) - why assignment (simple and compound) is
    right-associative.

    And here, as mentioned already, I see the unnecessary (and design-wise artificial) equalization of the two distinct language atoms, = and op=.


    Then there is (a) - why is the result of an assignment an rvalue, not
    an lvalue?˙ And why is it an lvalue in C++ ?

    I don't know an answer to that, other than that I don't think there
    would be any benefit of making it an lvalue in C.˙ (In C++, operators
    can be overloaded and may do other things - making the result an
    lvalue adds a little flexibility.)

    Yes, that's what I also think; it adds flexibility. And avoids an IMO unnecessary restriction; if there's any side-effect that matters in "C"
    I've yet to see one or heard from one. That's why I was asking.

    Janis

    PS: It might help understanding why it appears natural to me, consider
    its semantics "operate [sequentially] on a variable in an expression";
    here an example with the two possible Algol 68 syntaxes

    ˙ number˙ TIMESAB base˙ PLUSAB digit

    ˙ number *:= base +:= digit

    which in "C" would be

    ˙ number *= base;
    ˙ number += digit;

    or

    ˙ number = number * base + digit

    and in C++ either one of the "C" forms, or the parenthesized form

    ˙ (number *= base) += digit

    So, in C/C++, two separate statements, variable duplication, spurious parenthesis. All adding to the often mentioned "cognitive load".


    Multiple statements, duplicated variables and additional parentheses can
    all /reduce/ cognitive load sometimes - though they certainly can
    increase it in other cases. Overly compact code is just as bad as
    overly verbose code.


    If I saw "(number *= base) += digit;" in a code review, I would reject
    it as "smart-arse" coding. My preference would be something like :

    const auto scaled_number = unscaled_number * base + digit;

    My strong preference in coding is to initialise variables once, not
    modify them unless it really is helpful (or necessary, such as for
    loops). Unnecessary multiple modifications of one variable in one
    expression? No thanks.

    However, that is /my/ preference - other people like to do things
    differently. /My/ personal preferences were not a deciding factor in
    the design of C or C++ !



    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows
    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11
    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?





    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bartc@3:633/10 to All on Thu Nov 5 12:17:50 2026
    On 04/11/2026 07:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound" "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows

    ˙ i +:= 2 *:= 3 -:= 5 %*:= 11

    Apparently this means:

    i := ((i + 2) * 3 - 5) %* 11

    So it does not seem to buy you much. But doing it with ordinary
    assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.
    With a poor implementation, that will be less efficient

    * There is less ambiguity, for example:

    i +:= 2 *:= i;

    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.


    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    In my projects I don't allow such augmented or compound assignments to
    return any value at all. (It is too confusing, it is fiddly to
    implement, and there are too many surprises.)

    It is problematical in C too:

    int a = 1000;
    unsigned b = 200;
    int c = 300;

    a += b += 100;

    'a' ends up with the value 1044 rather than 1300. However you also see
    that with ordinary chained assignments:

    a = b = c;

    Both a and b end up with the value 44. So care is needed even here when
    terms are of mixed types.


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Thu Nov 5 13:22:50 2026
    On 05/11/2026 13:17, bartc wrote:
    On 04/11/2026 07:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows

    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11

    Apparently this means:

    ˙˙˙˙ i := ((i + 2) * 3 - 5) %* 11

    So it does not seem to buy you much. But doing it with ordinary
    assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.
    ˙ With a poor implementation, that will be less efficient

    * There is less ambiguity, for example:

    ˙ i +:= 2 *:= i;

    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.


    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    In my projects I don't allow such augmented or compound assignments to return any value at all. (It is too confusing, it is fiddly to
    implement, and there are too many surprises.)

    It is problematical in C too:

    ˙˙˙ int˙˙˙˙˙ a = 1000;
    ˙˙˙ unsigned b = 200;

    Just to be clear - you meant "unsigned char b = 200;" here? Mixing
    types can always risk being confusing or subtle, but I think we agree
    that using multiple assignments (simple or compound) in the same
    expression does nothing to help!

    ˙˙˙ int˙˙˙˙˙ c = 300;

    ˙˙˙ a += b += 100;

    'a' ends up with the value 1044 rather than 1300. However you also see
    that with ordinary chained assignments:

    ˙˙˙ a = b = c;

    Both a and b end up with the value 44. So care is needed even here when terms are of mixed types.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From fir@3:633/10 to All on Thu Nov 5 14:23:07 2026
    bartc pisze:
    On 04/11/2026 07:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows

    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11

    Apparently this means:

    ˙˙˙˙ i := ((i + 2) * 3 - 5) %* 11

    So it does not seem to buy you much. But doing it with ordinary
    assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.
    ˙ With a poor implementation, that will be less efficient

    * There is less ambiguity, for example:

    ˙ i +:= 2 *:= i;

    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.


    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    In my projects I don't allow such augmented or compound assignments to return any value at all. (It is too confusing, it is fiddly to
    implement, and there are too many surprises.)

    It is problematical in C too:

    ˙˙˙ int˙˙˙˙˙ a = 1000;
    ˙˙˙ unsigned b = 200;
    ˙˙˙ int˙˙˙˙˙ c = 300;

    ˙˙˙ a += b += 100;

    'a' ends up with the value 1044 rather than 1300. However you also see
    that with ordinary chained assignments:

    ˙˙˙ a = b = c;

    Both a and b end up with the value 44. So care is needed even here when terms are of mixed types.


    btw it is some question if giving this arithmetic precedence
    is good option

    normally arithmetic goes sal yeft to right so you have


    x=a+b*c //5 chars
    x=b*c+a //5 chars

    versus

    x=(a+b)*c //7 chars
    x=b*c+a //5 chars


    so the left to right would be shorter

    im not saying thet the higher * precedence is bad, only worth noticing
    its not the only option



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bartc@3:633/10 to All on Thu Nov 5 14:15:42 2026
    On 05/11/2026 12:22, David Brown wrote:
    On 05/11/2026 13:17, bartc wrote:

    It is problematical in C too:

    ˙˙˙˙ int˙˙˙˙˙ a = 1000;
    ˙˙˙˙ unsigned b = 200;

    Just to be clear - you meant "unsigned char b = 200;" here?

    Yes.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bartc@3:633/10 to All on Thu Nov 5 14:31:23 2026
    On 05/11/2026 13:23, fir wrote:
    bartc pisze:
    On 04/11/2026 07:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows

    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11

    Apparently this means:

    ˙˙˙˙˙ i := ((i + 2) * 3 - 5) %* 11

    So it does not seem to buy you much. But doing it with ordinary
    assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.
    ˙˙ With a poor implementation, that will be less efficient

    * There is less ambiguity, for example:

    ˙˙ i +:= 2 *:= i;

    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.


    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    In my projects I don't allow such augmented or compound assignments to
    return any value at all. (It is too confusing, it is fiddly to
    implement, and there are too many surprises.)

    It is problematical in C too:

    ˙˙˙˙ int˙˙˙˙˙ a = 1000;
    ˙˙˙˙ unsigned b = 200;
    ˙˙˙˙ int˙˙˙˙˙ c = 300;

    ˙˙˙˙ a += b += 100;

    'a' ends up with the value 1044 rather than 1300. However you also see
    that with ordinary chained assignments:

    ˙˙˙˙ a = b = c;

    Both a and b end up with the value 44. So care is needed even here
    when terms are of mixed types.


    btw it is some question if giving this arithmetic precedence
    is good option

    normally arithmetic goes sal yeft to right so you have


    x=a+b*c˙ //5 chars

    This is 7 characters in all. And normally it would be:

    x=a+b*c;

    which is 8 characters. Typically it will also be indented:

    x=a+b*c;

    which is 9 characters with a single hard tab. Some also like to add
    white space:

    x = a + b * c;

    which is 15 characters.

    x=b*c+a˙ //5 chars

    versus

    x=(a+b)*c //7 chars
    x=b*c+a˙˙ //5 chars


    so the left to right would be shorter

    im not saying thet the higher * precedence is bad, only worth noticing
    its not the only option

    Different precedences for the /arithmetic/ operators are good (ie * /
    are higher than + -) , because then the meaning aligns with how such expressions are universally understood, even in the real world.

    Having to type () is not an imposition if they are necessary. The
    character count is not too important (usually identifiers will be longer
    than single characters, plus see above); the number of tokens might be
    more relevant, as well as () being shifted.

    For the rest of the operators however C made some questionable choices
    so that often you have to use () because (1) the less natural and useful choice was made, or (2) to make it clear what's happening because the precedences are obscure, unfamiliar or unintuitive, even if technically
    not needed.

    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Richard Harnden@3:633/10 to All on Thu Nov 5 14:38:58 2026
    On 05/11/2026 14:31, bartc wrote:
    On 05/11/2026 13:23, fir wrote:
    bartc pisze:
    On 04/11/2026 07:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior >>>> that other languages designed differently; specifically the "compound" >>>> "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties; >>>> (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had >>>> mostly used C++, and I may have (wrongly!) assumed that "C" behaves the >>>> same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity. >>>>
    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows

    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11

    Apparently this means:

    ˙˙˙˙˙ i := ((i + 2) * 3 - 5) %* 11

    So it does not seem to buy you much. But doing it with ordinary
    assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.
    ˙˙ With a poor implementation, that will be less efficient

    * There is less ambiguity, for example:

    ˙˙ i +:= 2 *:= i;

    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.


    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    In my projects I don't allow such augmented or compound assignments
    to return any value at all. (It is too confusing, it is fiddly to
    implement, and there are too many surprises.)

    It is problematical in C too:

    ˙˙˙˙ int˙˙˙˙˙ a = 1000;
    ˙˙˙˙ unsigned b = 200;
    ˙˙˙˙ int˙˙˙˙˙ c = 300;

    ˙˙˙˙ a += b += 100;

    'a' ends up with the value 1044 rather than 1300. However you also
    see that with ordinary chained assignments:

    ˙˙˙˙ a = b = c;

    Both a and b end up with the value 44. So care is needed even here
    when terms are of mixed types.


    btw it is some question if giving this arithmetic precedence
    is good option

    normally arithmetic goes sal yeft to right so you have


    x=a+b*c˙ //5 chars

    This is 7 characters in all. And normally it would be:

    x=a+b*c;

    which is 8 characters. Typically it will also be indented:

    ˙˙˙ x=a+b*c;

    which is 9 characters with a single hard tab. Some also like to add
    white space:

    ˙˙˙ x = a + b * c;

    which is 15 characters.

    So what? Readability is more important that character count.


    x=b*c+a˙ //5 chars

    versus

    x=(a+b)*c //7 chars
    x=b*c+a˙˙ //5 chars


    so the left to right would be shorter

    im not saying thet the higher * precedence is bad, only worth noticing
    its not the only option

    Different precedences for the /arithmetic/ operators are good (ie * /
    are higher than + -) , because then the meaning aligns with how such expressions are universally understood, even in the real world.

    Having to type () is not an imposition if they are necessary. The
    character count is not too important (usually identifiers will be longer than single characters, plus see above); the number of tokens might be
    more relevant, as well as () being shifted.

    For the rest of the operators however C made some questionable choices
    so that often you have to use () because (1) the less natural and useful choice was made, or (2) to make it clear what's happening because the precedences are obscure, unfamiliar or unintuitive, even if technically
    not needed.

    C follows BODMAS (or PEMDAS) doesn't it?
    Nothing wrong with adding extra brackets if it make the intention clearer.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Thu Nov 5 23:41:43 2026
    On 2026-11-05 13:17, bartc wrote:
    On 04/11/2026 07:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior
    that other languages designed differently; specifically the "compound"
    "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.

    It might have been that it appeared not as serious to me because I had
    mostly used C++, and I may have (wrongly!) assumed that "C" behaves the
    same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity.

    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows

    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11

    Apparently this means:

    ˙˙˙˙ i := ((i + 2) * 3 - 5) %* 11

    Yes, this is one of the workarounds, especially in languages that
    don't support what I was using.


    So it does not seem to buy you much.

    We still have the advantages that I mentioned elsethread. But that
    was not my question.

    I remind: since they have introduced op= to return a value why did
    they do it the way they did. Specifically, what are the use-cases
    that they support by their implementation semantics (with rvalues
    and right-associativity).

    But doing it with ordinary assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.

    This appears to me to be nonsensical given that we are talking about abstractions in a higher-level language, and that the transformation
    to machine instructions need not even result in different code.

    ˙ With a poor implementation, that will be less efficient

    With poor implementation everything will be less efficient.

    (What sort of "arguments" are you spreading!)


    * There is less ambiguity, for example:

    Ambiguity appears if you make up ambiguous samples unnecessarily, as
    you do so often, and as you seem to do here.


    ˙ i +:= 2 *:= i;

    What is that supposed to implement in the first place is the question!

    I suggest to explain a sensible task, and then we can choose what the
    most appropriate coding pattern in any given language might be.

    Upthread I gave such an example with a real application and that makes
    use of the clear semantics that, e.g., the Algol 68 construct offers:

    number *:= base +:= digit


    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.

    Are you now speaking about a left-associative interpretation?

    Anyway, you seem to not see the obvious change of 'i' with '+:=', so
    I cannot help you.



    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?

    In my projects I don't allow such augmented or compound assignments to return any value at all. (It is too confusing, it is fiddly to
    implement, and there are too many surprises.)

    I understand that it is confusing to you. I understand that you seem
    to consider it to be "fiddly". I understand that you are incapable of
    defining a clear semantics so that it won't surprise anyone.

    I'm fine with your incapabilities. (No one is perfect.) - FWIW, I've
    been using that construct in Algol 68 and (with parentheses) in C++
    without any confusion or observing any surprises.


    It is problematical in C too:

    ˙˙˙ int˙˙˙˙˙ a = 1000;
    ˙˙˙ unsigned b = 200;
    ˙˙˙ int˙˙˙˙˙ c = 300;

    ˙˙˙ a += b += 100;

    'a' ends up with the value 1044 rather than 1300.

    Huh?

    As I read it it would (in "C") produce 1300. And a test confirms that.

    However you also see
    that with ordinary chained assignments:

    ˙˙˙ a = b = c;

    Both a and b end up with the value 44. So care is needed even here when terms are of mixed types.

    Huh?

    Are you hallucinating?


    To summarize your response: you find it confusing, hard ("fiddly") to
    implement in a simple and clear way, and the results of that feature
    would anyway surprise you.

    Okay, thanks. But all that doesn't surprise me since that's obviously
    problems you seem to have.

    Janis


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Fri Nov 6 01:19:22 2026
    On 2026-11-05 11:33, David Brown wrote:
    On 05/11/2026 03:19, Janis Papanagnou wrote:
    Thanks for your answer. (I'll keep all your explanations for context.)
    My comments below...

    On 2026-11-04 10:56, David Brown wrote:
    On 04/11/2026 08:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior >>>> that other languages designed differently; specifically the "compound" >>>> "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two properties; >>>> (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.


    I don't see a distinction here between compound assignment and simple
    assignment - the compound assignment operators follow the simple
    assignment operator for consistency, and because "x += y" is defined
    to mean "x = x + y".˙ So I think that we can just talk about simple
    assignment (or at least, talk about it first).

    Okay, I see where you're coming from. - Actually that reasoning is one
    of the couple rationales that the AI provided me as answer as well.

    Since I don't see the need to not have a distinction between = and op=
    I dismissed that. In consequence, as you say or imply, all you derive
    from that is thus also not convincing to me. - Let me expand on that...

    I can agree that op= does not /need/ to be consistent with simple
    assignment for associativity and rvalue/lvalue results, but I think a language designer would need very strong reasons before allowing them to
    be inconsistent.

    You may have missed that I don't consider it "inconsistent". (The
    reasons have already been explained.)

    (I understood that with your _equalization_ of the = and op= classes
    of operators you find it "inconsistent". As an opinion I accept it.)



    First allow that I see = and op= as different things. (Note that, e.g.,
    Algol 68 also differentiates here, so it's not a given that we handle
    them the same or semantically similar.)

    Okay.˙ I am quite happy to accept that and see where it leads us.˙ (Note that I have almost no knowledge of Algol 68, if that is relevant.)

    (I don't think specific knowledge is necessary here. But we'll see...
    It's anyway primarily about "C", and a bit about C++ here.)


    Allow having, at the design
    stage, the option to just define the most sensible characteristics to
    allow a consistent yet flexible use without "arbitrary" restrictions.


    Okay.

    Given that cascading is considered useful and also used in practice I'd
    like to consider that from another perspective; from use-cases.

    Cascading is also considered unclear and questionable style, with poorly defined semantics in special cases (such as volatile accesses) or if
    there are other side effects involved.

    Here you are speaking about 'volatile' in "C", right? - I've not
    engaged with 'volatile' deeply enough to comment on that. - I'm
    only asking whether that's a problem with the 'volatile' concept
    or the op= logic as it's defined. (And whether 'volatile' and the
    op= question is the only problem here - i.e. the "bad feature" is
    the op= - where 'volatile' won't show any other problems with all
    the other "C" constructs.) Concerning _side-effects_ is a general
    problem in "C"; bart has in a recent post made up a side-effect
    sample (to criticize op= , not to criticize writing side-effects).


    Different people will have different opinions on what should be allowed
    or disallowed in a language - and when they are willing to disallow some useful cases to ensure dangerous cases are compile-time errors, or when
    they are willing to allow some dangerous cases to ensure that useful
    cases are allowed.

    (Well, sure. That's why I asked for [sensible] use-cases.)


    However the balance is picked for a language, it is inevitable that some potentially useful cases will be disallowed in order to reduce the risk
    of subtle errors in code.

    (Still not obvious what that actually would be. - No offense intended
    but sounds more like FUD. That's why I asked for something concrete.)

    I can't see how op= expressions with rvalues and right-associativity
    semantics would in any way be [significantly] more "risky" than the
    C-form with lvalues and right associativity, or the C++-form with
    rvalues and right-associativity.



    What use-cases are there that would make it sensible to allow (in "C")
    just rvalues, to prevent which misuses, or prevent what ambiguities, or
    which problems with compilers, or would introduce any other problem?

    Perhaps my imagination is lacking.

    Mine as well. - That's why I was asking; if we'd have a convincing
    example it could answer my original question. (So I can meanwhile
    just speculate that it might be the reason that also Kaz mentioned
    in his post.)


    For simple assignment, I can't think of any.˙ "(a = b) = c;", if it were allowed in C, would mean the same as "a = b; a = c;" except that "a" is
    only evaluated once.

    (But we had that already.)

    (Imagine "a" was "xs[i++]" to see why that matters.)

    Again primarily an example of a C-typical side-effect with the '++'
    operators. - I can point to what I mentioned to bart; the point is
    not to make up some example that will "hurt", but to use the most
    appropriate code patterns for any given functionality.

    The only reason to write "a = b; a = c;" would be for the
    side-effects, such as if "b" and "c" are function calls or volatile variables.˙ And I cannot see any circumstances where "(a = b) = c;"
    would be seen as clearer than "b; a = c;" or - if you want it as a
    single expression - "b, a = c;".

    For compound assignment, "(a += b) += c;" would mean (ignoring multiple evaluations of "a") :

    ˙˙˙˙a = a + b;
    ˙˙˙˙a = a + c;

    The only circumstances I can think of where "(a += b) += c" would be preferable to "a += (b + c)", is if the calculation order affected
    signed integer overflow or rounding in floating point.˙ And then I think
    the code would be far better written as "a = (a + b) + c;".

    I'm not sure what the sample with '+' and '+=' serves here. (I'd have
    thought my example with mixed operations would fit better explaining
    things; it was: number *= base += digit; .)

    WRT "a = (a + b) + c;" I'd just repeat my key-words: duplication and parentheses. In "C" you have anyway only the choices already mentioned,
    so just use what's existing.



    What use-cases are there that would make it sensible to right-associate
    (as in "C" and C++) modifications of an lvalue.

    In C++, functions can return references that are lvalues.˙ Operators can
    be overloaded for user types, as functions.˙ It is therefore consistent
    in the language that the result of assignment operators is an lvalue.
    But I don't see use-cases for lvalue results of assignment operators on standard types.

    I see the additional advantages for C++.

    And I see use-cases for lvalues in "C". (See above.)



    (Please keep in mind that there's yet no substantial need identified
    to consider the two syntactic elements similar.)

    I wouldn't think that a C-programmer would have difficulties to confuse
    semantics of = and op= (especially given that there's many a lot more
    confusing things in "C" as, e.g., the = and == choice). So "consistency"
    is as argument vague at best.

    "C is so confusing that it doesn't matter if we make it worse" does not carry much weight with me.˙ Again - I think it would be fine to have an inconsistency between simple and compound assignment, but only if there
    was a very strong reason /for/ the inconsistency.˙ So far, you have not given any reason that I can see.

    You probably got me wrong; I wasn't asking for a change of "C".

    I was just interested in the rationale for the given C-design. - I.e.
    that they have chosen to make op= (and =) _expressions_, but that they
    haven't chosen two useful properties, namely returning lvalues, and
    defining left-associativity. - And given that they had such existing
    semantics from another language as paragon, a language they already
    considered, and probably where from they may even have adopted the expression-type of the = and op= operators. - IOW; why they jumped
    in that direction but didn't realized it in a way that would be IMO
    more sensible.

    Besides you wrote here again about "inconsistency" where there isn't
    one, as explained. (YMMV.)

    And examples have been given. I used such construct to my advantage to
    write clear code. (Of course I accept that you may find them not being worthwhile. Your opinion is fine but in that light irrelevant for me.)



    I also understand what you wrote below that you'd preferred to not have
    the issues in the first place by having "var op= val" being a statement.

    (Since "C" is now actually returning a value the question remains why
    the two properties - that I formulated as questions - were designed as
    they are.)

    A design where you can cascade the op= as a pipeline processing appears
    quite appealing and natural to me (and I certainly used it; in C++ with
    the parenthesis necessary, and in Algol 68 I used it straightforward).

    Could you give some examples of how you use it in C++?˙ That would, I
    think, make it a lot clearer to me why you think this is a good idea.

    Sorry, I can provide no production code; my professional C++ days were
    in the 1990's and I have no access to the code. All I remember was that
    I occasionally used such cascades, and that I had to write "spurious" parenthesis to achieve the left-associativity.



    So far on the general view "from outside".


    If you figure out (a), then (b) is the only possible choice.˙ That
    is, if you write

    ˙˙˙˙˙x = y = z;

    it could not be interpreted as

    ˙˙˙˙˙(x = y) = z;

    unless "x = y" were an lvalue.

    Assignment with right-associativity I consider natural and all is fine.


    For simple assignment, if "x = y = z" were left associative, and "x =
    y" were an lvalue, that would mean that instead of being

    ˙˙˙˙˙y = z;
    ˙˙˙˙˙x = y;

    it would now mean :

    ˙˙˙˙˙x = y;
    ˙˙˙˙˙x = z;

    which is clearly not a useful construct.

    Right, it's not a useful construct for assignments.


    (Personally, I'd rather that assignment had been a kind of statement
    rather than an expression - but if a language is going to make
    assignment an expression, it's better that it is a potentially useful
    one!)

    All fine for assignments.


    I think that all covers (b) - why assignment (simple and compound) is
    right-associative.

    And here, as mentioned already, I see the unnecessary (and design-wise
    artificial) equalization of the two distinct language atoms, = and op=.


    Then there is (a) - why is the result of an assignment an rvalue, not
    an lvalue?˙ And why is it an lvalue in C++ ?

    I don't know an answer to that, other than that I don't think there
    would be any benefit of making it an lvalue in C.˙ (In C++, operators
    can be overloaded and may do other things - making the result an
    lvalue adds a little flexibility.)

    Yes, that's what I also think; it adds flexibility. And avoids an IMO
    unnecessary restriction; if there's any side-effect that matters in "C"
    I've yet to see one or heard from one. That's why I was asking.

    Janis

    PS: It might help understanding why it appears natural to me, consider
    its semantics "operate [sequentially] on a variable in an expression";
    here an example with the two possible Algol 68 syntaxes

    ˙˙ number˙ TIMESAB base˙ PLUSAB digit

    ˙˙ number *:= base +:= digit

    which in "C" would be

    ˙˙ number *= base;
    ˙˙ number += digit;

    or

    ˙˙ number = number * base + digit

    and in C++ either one of the "C" forms, or the parenthesized form

    ˙˙ (number *= base) += digit

    So, in C/C++, two separate statements, variable duplication, spurious
    parenthesis. All adding to the often mentioned "cognitive load".


    Multiple statements, duplicated variables and additional parentheses can
    all /reduce/ cognitive load sometimes - though they certainly can
    increase it in other cases.˙ Overly compact code is just as bad as
    overly verbose code.

    Yes, I agree.



    If I saw "(number *= base) += digit;" in a code review, I would reject
    it as "smart-arse" coding.

    Well, that's obviously the effect when one is reviewing code based on
    one's personal opinion rather than based on a coding standard document
    which has rules with rationales associated to every rule.

    (None of the various coding standards I was involved in creating had a "smart-arse" type of "rationale".)

    My preference would be something like :

    ˙˙˙˙const auto scaled_number = unscaled_number * base + digit;

    Different use-case. My code was an excerpt from incrementally creating
    the number in a loop. Here's some Algol 68 code for _illustration_ only
    (i.e. non-optimized, and using the verbose word-operators instead of
    the terse forms, just written just for the post)

    STRING numstr = "12345";
    INT number := 0;
    FOR i TO UPB numstr DO
    number TIMESAB 10 PLUSAB (ABS numstr[i] - ABS "0")
    OD

    Another cascade I was recently writing (with two 'op=' "pipelines") was

    FOR i FROM 1 TO UPB key DO
    hash PLUSAB ABS key[i] * pp MODAB hash_modulus;
    pp TIMESAB p MODAB hash_modulus
    OD


    My strong preference in coding is to initialise variables once, not

    In case of 'op=' you *have* to initialize the variable on the LHS!

    modify them unless it really is helpful (or necessary, such as for
    loops).˙ Unnecessary multiple modifications of one variable in one expression?˙ No thanks.

    However, that is /my/ preference - other people like to do things differently.˙ /My/ personal preferences were not a deciding factor in
    the design of C or C++ !

    That applies to most people. :-)

    But it's noteworthy, IMO, if you saw discrepancies or infelicities in
    a language, and while you can't actually do anything about it, you see
    that then changed in some standard two decades later. Or if you see
    some time later that the authors of a language are self-critically
    speaking about such cases. - All this makes it worthwhile to discuss
    the state of things, languages, tools, and standards. - And don't just
    accept things as "final wisdom".

    Janis

    PS: I noticed that you repeated things in your post that I had already
    been answering. I answered them again in case they were overlooked.
    But it makes no sense to continue the subthread that way.




    It might have been that it appeared not as serious to me because I had >>>> mostly used C++, and I may have (wrongly!) assumed that "C" behaves the >>>> same way. But sadly, no, they differ here.

    ˙˙ i += 2 *= 3 -= 5 %= 11;˙˙˙˙˙˙˙ // impossible in C and C++
    ˙˙ (((i += 2) *= 3) -= 5) %= 11;˙ // impossible in C, possible in C++

    But C++ still requires parentheses because of the right-associativity. >>>>
    (I suppose the rvalue to lvalue semantical design variant in C++ was
    to facilitate such cascades?
    And the left-associativity was maybe considered to be too drastic a
    change compared to the (partly) inherited "C" design?
    But my primary question here is about the "C" design rationale!)

    Note: My question is not about possible workarounds. (I know them.)

    Janis

    [*] I emphasized "substantial" because what the Google AI "explained"
    to me wasn't at all convincing.

    PS: Algol 68 (for example) had also a left-associativity which allows
    ˙˙ i +:= 2 *:= 3 -:= 5 %*:= 11
    and given that the "C" authors knew that language but deviated from
    that paragon there must (or might) have been some thoughts about why
    it is as it is?






    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From bartc@3:633/10 to All on Fri Nov 6 00:27:15 2026
    On 05/11/2026 22:41, Janis Papanagnou wrote:
    On 2026-11-05 13:17, bartc wrote:

    We still have the advantages that I mentioned elsethread. But that
    was not my question.

    I remind: since they have introduced op= to return a value why did
    they do it the way they did. Specifically, what are the use-cases
    that they support by their implementation semantics (with rvalues
    and right-associativity).

    Algol68 had this big idea that everything has to return some value,
    including compound assignment. Given that, it made these choices, which includes ordinary assignment having RTL precedence, and compound
    assignment being LTR.

    Personally I don't see any great need to allow compound assignments to
    be chained at all, as it only leads to harder-to-understand code with
    too many things being updated within the same statement.

    So my compound assignments return 'void'.


    But doing it with ordinary assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.

    This appears to me to be nonsensical given that we are talking about abstractions in a higher-level language, and that the transformation
    to machine instructions need not even result in different code.

    ˙˙ With a poor implementation, that will be less efficient

    With poor implementation everything will be less efficient.

    number *:= base +:= digit

    I did a brief speed test using A68G. That version was 50% slower than:

    number := number * base + digit;


    ˙˙ i +:= 2 *:= i;

    What is that supposed to implement in the first place is the question!

    I suggest to explain a sensible task,
    The task doesn't matter. The behaviour needs to be well-defined. And
    here that might be unintuitive.

    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.

    Are you now speaking about a left-associative interpretation?

    I'm speaking about observed behaviour from A68G:

    INT i:=100;
    i +:= 2 *:= i;
    print(i)

    Output is 10404, or 102 * 102. It is not the same as either of:

    i := i + 2 * i; # 300 #
    i := (i + 2) * i; # 10200 #

    It is also confusing in that here:

    number *:= base +:= digit

    It looks as though 'base' is being modified too.


    Anyway, you seem to not see the obvious change of 'i' with '+:=', so
    I cannot help you.

    That's the confusing bit; when does the change happen? Here:

    i +:= a +:= b +:= i +:= c +:= i

    presumably at the first +:=, all the 'i' instances on the right
    simultaneously change their values, and by the second +:=, it's all
    change once more?!

    But by the fourth +:=, the value of the middle 'i' is no longer a factor.



    I understand that it is confusing to you. I understand that you seem
    to consider it to be "fiddly". I understand that you are incapable of defining a clear semantics

    The point is that the semantics are necessarily more obscure and less
    obvious compared with plain code which only has one assignment.

    so that it won't surprise anyone.

    I understand that you've never had to implement any of this stuff.

    It is problematical in C too:

    ˙˙˙˙ int˙˙˙˙˙ a = 1000;
    ˙˙˙˙ unsigned b = 200;
    ˙˙˙˙ int˙˙˙˙˙ c = 300;

    ˙˙˙˙ a += b += 100;

    'a' ends up with the value 1044 rather than 1300.

    Are you hallucinating?

    No. 'b' was supposed to have type 'unsigned char' as I confirmed when DB queried it. Interesting, the difference in attitude.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Janis Papanagnou@3:633/10 to All on Fri Nov 6 10:25:28 2026
    On 2026-11-06 01:27, bartc wrote:
    On 05/11/2026 22:41, Janis Papanagnou wrote:
    On 2026-11-05 13:17, bartc wrote:
    [...]

    ˙˙ i +:= 2 *:= i;

    What is that supposed to implement in the first place is the question!
    I suggest to explain a sensible task,

    The task doesn't matter.

    For a sensible consideration a sensible example (not a made up
    one) from a practical application is imperative. Otherwise, as
    was already pointed out multiple times in various communications
    with you, you stupidly construct just bad code that no one would
    write that way in the first place and for no practical use.

    The behaviour needs to be well-defined.

    The fine thing here is that programming languages are typically
    "well-defined". (Algol 68 is. In "C" there's unfortunately quite
    some places where you need a lot deep knowledge to avoid things
    that are unreliable because of being undefined, yet accepted by
    the compilers.)

    And here that might be unintuitive.

    What's intuitive is a subjective matter. You said already that
    you have problems with it, that it "confuses" you. (Repetitions
    don't make your opinions and your personal problems a fact.)


    The second 'i' will have the new value i + 2, not the original, even
    though it has not obviously been modified.

    Are you now speaking about a left-associative interpretation?

    I'm speaking about observed behaviour from A68G:

    Okay, so you speak about a left-associative interpretation here.


    ˙INT i:=100;
    ˙i +:= 2 *:= i;
    ˙print(i)

    Hardly any professional programmer would write such expressions
    with side-effects; "C" is full of such. ("If it hurts just don't
    do it!")

    It would really help to make the discussion more serious if you'd
    choose sensible examples without side-effects, which always cause
    confusion in any language or language-construct where it's used!


    Output is 10404, or 102 * 102. It is not the same as either of:

    ˙ i := i + 2 * i;˙˙˙˙˙˙ # 300 #
    ˙ i := (i + 2) * i;˙˙˙˙ # 10200 #

    Your example appears still nonsensical and bad. I suggested using
    examples from real applications and observe the various choices
    you have to model such functionality in the respective languages.


    It is also confusing in that here:

    ˙ number *:= base +:= digit

    It looks as though 'base' is being modified too.

    Only if you don't know the semantics. - You've been often told (WRT
    "C") that you should learn the language you use. It's not different
    with other languages.

    I don't know about you. I started programming with a comparably lousy programming language (with strong focus on structuring with 'goto's).
    Whenever I learned a new language with new, own paradigms I made use
    of its features and modelling styles wherever I saw them applicable
    in useful ways for my software. Be it OO, functional, declarational,
    whatever. If you are lacking that flexibility, well, just stay with
    what serves you, use something that doesn't confuse you.

    You regularly say you have problems also with "C", and you regularly
    complain about it. - What's your goal with all your complaints; be
    it "C" in general, or this specific Algol 68 feature? We meanwhile all
    know that there's so much that confuses you. We know that you prefer
    to write (despite the necessary effort and the plethora of existing
    languages of different kinds you could choose from) own compilers for self-designed languages that don't confuse you. - What's your goal?
    Do you want to just get confirmations from other folks that they are
    as confused as you are? (Even despite they are not confused?)

    [ snip another, yet more pathological example with side-effects ]

    I understand that it is confusing to you. I understand that you seem
    to consider it to be "fiddly". I understand that you are incapable of
    defining a clear semantics

    The point is that the semantics are necessarily more obscure and less obvious compared with plain code which only has one assignment.

    But they aren't at all obscure. The semantics are really most simple!
    Process the expression in your mind like a "pipe" (or like any other
    numerical or typical string expressions) just from left to right.

    But (in Algol 68) you should also understand that expressions may not
    only be comprised of "level-0-values" but also that "level-1-values" (references, "names", "variables") can be returned. Maybe different
    from the languages you know but all most consistent; Algol 68 is known
    for its orthogonality and consistency, especially if compared with "C"
    or similar. But since it's different you have to learn its concepts
    (if you intend to use it, and also if you want to dispute about it).

    But at that point I want to remember the intention of my thread. - It
    was not to advertise Algol 68, but to ask why "C" invented op= as part
    of expressions (as opposed to a statement) with semantics to return a
    value and right associativity.


    so that it won't surprise anyone.

    I understand that you've never had to implement any of this stuff.

    What "this stuff"? - You mean side-effects, as in your pathological
    examples above? - Yes, you are right. I try to avoid all side effects.
    And I certainly wouldn't artificially add them in my code just "for a
    kick".

    Or are you speaking about writing a compiler that would have this (in
    your opinion) "huge difficulty" implementing a proper semantics for
    these sorts of expressions? - Others have done that and I've not heard
    that this (IMO primitive) detail would have caused any implementation
    problems. (Algol 68 certainly has other corners where very demanding proficiencies are required.)

    Janis

    [...]


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Nov 6 10:36:27 2026
    (I'm snipping a bit for brevity.)

    On 06/11/2026 01:19, Janis Papanagnou wrote:
    On 2026-11-05 11:33, David Brown wrote:
    On 05/11/2026 03:19, Janis Papanagnou wrote:
    Thanks for your answer. (I'll keep all your explanations for context.)
    My comments below...

    On 2026-11-04 10:56, David Brown wrote:
    On 04/11/2026 08:53, Janis Papanagnou wrote:
    I just stumbled about something in "C" where I knew there was behavior >>>>> that other languages designed differently; specifically the "compound" >>>>> "and-becomes" operators (+=, -=, *=, /=, %=).

    I'm basically looking for substantial[*] rationales for two
    properties;
    (a) that their result is an rvalue (and not an lvalue), and
    (b) that their evaluation in cascades has right-associativity.


    I don't see a distinction here between compound assignment and
    simple assignment - the compound assignment operators follow the
    simple assignment operator for consistency, and because "x += y" is
    defined to mean "x = x + y".˙ So I think that we can just talk about
    simple assignment (or at least, talk about it first).

    Okay, I see where you're coming from. - Actually that reasoning is one
    of the couple rationales that the AI provided me as answer as well.

    Since I don't see the need to not have a distinction between = and op=
    I dismissed that. In consequence, as you say or imply, all you derive
    from that is thus also not convincing to me. - Let me expand on that...

    I can agree that op= does not /need/ to be consistent with simple
    assignment for associativity and rvalue/lvalue results, but I think a
    language designer would need very strong reasons before allowing them
    to be inconsistent.

    You may have missed that I don't consider it "inconsistent". (The
    reasons have already been explained.)

    No, I did not. But I did think more about repeated use of the same op=, rather than mixes of different op's.


    (I understood that with your _equalization_ of the = and op= classes
    of operators you find it "inconsistent". As an opinion I accept it.)

    Sure. It's all just opinions here, not facts. I am not suggesting that
    I think my opinions are universal in any sense. It may be that the
    founding fathers of C shared some of my opinions and that influenced
    their design decisions - but I do not know anything specific there.


    Given that cascading is considered useful and also used in practice I'd
    like to consider that from another perspective; from use-cases.

    Cascading is also considered unclear and questionable style, with
    poorly defined semantics in special cases (such as volatile accesses)
    or if there are other side effects involved.

    Here you are speaking about 'volatile' in "C", right? - I've not
    engaged with 'volatile' deeply enough to comment on that. - I'm
    only asking whether that's a problem with the 'volatile' concept
    or the op= logic as it's defined. (And whether 'volatile' and the
    op= question is the only problem here - i.e. the "bad feature" is
    the op= - where 'volatile' won't show any other problems with all
    the other "C" constructs.)

    "volatile" turns up a lot in my field, but is not nearly as common for
    most C and C++ programmers. (Well, ultimately it /is/ common, or your programs would have little or no "observable behaviour", but it is
    deeply hidden within the libraries, system calls, etc.)

    C++ has deprecated many uses of "volatile", including its use in
    compound assignments, precisely because the semantics were unclear and
    often inconsistently implemented (even within the same compiler!). The
    point of using "volatile" variables is that each read and each write
    should be fully determined and ordered, which is difficult with complex expressions like those under discussion.

    Concerning _side-effects_ is a general
    problem in "C"; bart has in a recent post made up a side-effect
    sample (to criticize op= , not to criticize writing side-effects).


    I would definitely be unhappy with cascaded assignment (simple or
    compound) where there were side-effects of any kind.

    My point was just that while it is true that "cascading is considered
    useful" by /some/ people - such as yourself - it is also considered a
    bad idea by other people (such as me). These are both subjective
    opinions, and I don't think there is any kind of consensus.



    Different people will have different opinions on what should be
    allowed or disallowed in a language - and when they are willing to
    disallow some useful cases to ensure dangerous cases are compile-time
    errors, or when they are willing to allow some dangerous cases to
    ensure that useful cases are allowed.

    (Well, sure. That's why I asked for [sensible] use-cases.)


    You are the one who uses these cascades, and who writes expressions in
    C++ that use the lvalue nature of the result of assignment operators.
    To me, with my preferences and style of coding, there are /no/ sensible use-cases. There may be cases that other people see as sensible.


    However the balance is picked for a language, it is inevitable that
    some potentially useful cases will be disallowed in order to reduce
    the risk of subtle errors in code.

    (Still not obvious what that actually would be. - No offense intended
    but sounds more like FUD. That's why I asked for something concrete.)

    It was a general statement, but it might not have been helpful here.


    I can't see how op= expressions with rvalues and right-associativity semantics would in any way be [significantly] more "risky" than the
    C-form with lvalues and right associativity, or the C++-form with
    rvalues and right-associativity.

    (It's C++ that has lvalue results, and C that has rvalue results - I
    think you have mixed up your rvalue's and lvalue's here. But I know
    what you meant.)

    Look at it from a different viewpoint. If C's assignment operators
    (simple and/or compound) evaluated to lvalues, what could you write that
    you cannot do with rvalues? "a += b *= c". Is that "risky" ? I think
    it would easily be misunderstood - though of course these things depend
    on how often you see such code.




    What use-cases are there that would make it sensible to allow (in "C")
    just rvalues, to prevent which misuses, or prevent what ambiguities, or
    which problems with compilers, or would introduce any other problem?

    Perhaps my imagination is lacking.

    Mine as well. - That's why I was asking; if we'd have a convincing
    example it could answer my original question. (So I can meanwhile
    just speculate that it might be the reason that also Kaz mentioned
    in his post.)

    If we do /not/ have a convincing example, then that is actually better progress towards answering your original question. If evaluating to an
    lvalue allows people to write very questionably code, but does not allow significant good code, then that is a good reason to say the evaluation
    is an rvalue.

    (I am not at all convinced by Kaz's suggestion that the decisions were
    made "without any thought". I do not believe the C designers and/or standardisers thought of all possible scenarios or code styles, but I
    would be surprised if they did not make the rvalue/lvalue decision
    actively. And given that C++ actively picked lvalue, the C committee
    could easily have seen that and changed C to match without affecting
    existing valid code, if they thought it was a good idea.)


    The only circumstances I can think of where "(a += b) += c" would be
    preferable to "a += (b + c)", is if the calculation order affected
    signed integer overflow or rounding in floating point.˙ And then I
    think the code would be far better written as "a = (a + b) + c;".

    I'm not sure what the sample with '+' and '+=' serves here. (I'd have
    thought my example with mixed operations would fit better explaining
    things; it was:˙ number *= base += digit; .)


    Fair enough.

    But I would have the same reasoning for "(a *= b) + c".

    WRT˙ "a = (a + b) + c;"˙ I'd just repeat my key-words: duplication and parentheses. In "C" you have anyway only the choices already mentioned,
    so just use what's existing.



    What use-cases are there that would make it sensible to right-associate
    (as in "C" and C++) modifications of an lvalue.

    In C++, functions can return references that are lvalues.˙ Operators
    can be overloaded for user types, as functions.˙ It is therefore
    consistent in the language that the result of assignment operators is
    an lvalue. But I don't see use-cases for lvalue results of assignment
    operators on standard types.

    I see the additional advantages for C++.

    One plausible use-case I see for C++ is when operators are used for an entirely different purpose. For example, "cout << a << b;" has nothing
    to do with shifts. If you wanted a right-associative operator, you
    could use "b >>= a >>= stack". This would require "a >>= stack" to be
    an lvalue. Of course, this applies to user-defined types and operator overloads, and does not require rvalue results from standard type
    assignment operators.


    And I see use-cases for lvalues in "C". (See above.)

    I don't see "number *= base += digit;" as a useful use-case in C or C++.
    I see it as a rather poor way to write code. I don't see saving a duplication of an identifier or a set of parentheses as an advantage.
    But I appreciate that /you/ do.




    (Please keep in mind that there's yet no substantial need identified
    to consider the two syntactic elements similar.)

    I wouldn't think that a C-programmer would have difficulties to confuse
    semantics of = and op= (especially given that there's many a lot more
    confusing things in "C" as, e.g., the = and == choice). So "consistency" >>> is as argument vague at best.

    "C is so confusing that it doesn't matter if we make it worse" does
    not carry much weight with me.˙ Again - I think it would be fine to
    have an inconsistency between simple and compound assignment, but only
    if there was a very strong reason /for/ the inconsistency.˙ So far,
    you have not given any reason that I can see.

    You probably got me wrong; I wasn't asking for a change of "C".

    I realise that you are not trying to change C as it is now. (Though if someone felt strongly enough that assignment should return an lvalue,
    they could write a proposal for C2y. Changing the associativity would
    be out of the question, but changing the value type would, I think, be feasible.)


    Could you give some examples of how you use it in C++?˙ That would, I
    think, make it a lot clearer to me why you think this is a good idea.

    Sorry, I can provide no production code; my professional C++ days were
    in the 1990's and I have no access to the code. All I remember was that
    I occasionally used such cascades, and that I had to write "spurious" parenthesis to achieve the left-associativity.

    Sure. I was not asking for real-life code, merely snippets for
    demonstration purposes. You have given the one "number *= base +=
    digit" example that I do not find convincing. If you had more, that
    would carry more weight.



    So, in C/C++, two separate statements, variable duplication, spurious
    parenthesis. All adding to the often mentioned "cognitive load".


    Multiple statements, duplicated variables and additional parentheses
    can all /reduce/ cognitive load sometimes - though they certainly can
    increase it in other cases.˙ Overly compact code is just as bad as
    overly verbose code.

    Yes, I agree.



    If I saw "(number *= base) += digit;" in a code review, I would reject
    it as "smart-arse" coding.

    Well, that's obviously the effect when one is reviewing code based on
    one's personal opinion rather than based on a coding standard document
    which has rules with rationales associated to every rule.

    (None of the various coding standards I was involved in creating had a "smart-arse" type of "rationale".)

    Rules such as "keep expressions simple", "do one thing at a time", "the
    result of an assignment operator shall not be used" are very common in
    coding standards. Sometimes they have specific black-or-white rules
    (such as the MISRA rule "the result of an assignment operator shall not
    be used"). Sometimes they have more nebulous formulations (like the C++
    Core Guidelines rule "Avoid complicated expressions", or the JSF rule
    limiting the number of side-effects in a single operation).

    I agree it would be unlikely to see the term "smart-arse" in a formal
    coding standard :-)


    My preference would be something like :

    ˙˙˙˙˙const auto scaled_number = unscaled_number * base + digit;

    Different use-case. My code was an excerpt from incrementally creating
    the number in a loop. Here's some Algol 68 code for _illustration_ only
    (i.e. non-optimized, and using the verbose word-operators instead of
    the terse forms, just written just for the post)

    ˙˙˙ STRING numstr = "12345";
    ˙˙˙ INT number := 0;
    ˙˙˙ FOR i TO UPB numstr DO
    ˙˙˙˙˙˙˙ number˙ TIMESAB 10˙ PLUSAB (ABS numstr[i] - ABS "0")
    ˙˙˙ OD

    Another cascade I was recently writing (with two 'op=' "pipelines") was

    ˙˙˙ FOR i FROM 1 TO UPB key DO
    ˙˙˙˙˙˙˙ hash˙ PLUSAB ABS key[i] * pp˙ MODAB hash_modulus;
    ˙˙˙˙˙˙˙ pp˙˙˙ TIMESAB p˙˙˙˙˙˙˙˙˙˙˙˙˙˙ MODAB hash_modulus
    ˙˙˙ OD


    My strong preference in coding is to initialise variables once, not

    In case of 'op=' you *have* to initialize the variable on the LHS!


    Obviously, yes. My preference would be to initialise the variables
    once, and therefore not use an "op=" expression. Of course sometimes, especially in loops, you do need to change the value of a variable.
    This is C, not a pure functional language.

    modify them unless it really is helpful (or necessary, such as for
    loops).˙ Unnecessary multiple modifications of one variable in one
    expression?˙ No thanks.

    However, that is /my/ preference - other people like to do things
    differently.˙ /My/ personal preferences were not a deciding factor in
    the design of C or C++ !

    That applies to most people. :-)

    But it's noteworthy, IMO, if you saw discrepancies or infelicities in
    a language, and while you can't actually do anything about it, you see
    that then changed in some standard two decades later. Or if you see
    some time later that the authors of a language are self-critically
    speaking about such cases. - All this makes it worthwhile to discuss
    the state of things, languages, tools, and standards. - And don't just
    accept things as "final wisdom".

    Agreed.

    It is also the case that we all use subsets of languages, and it can be interesting to discuss which features we include in our subsets, and
    which we exclude. Such discussions would be rather boring if we all
    agreed on our subsets!


    Janis

    PS: I noticed that you repeated things in your post that I had already
    been answering. I answered them again in case they were overlooked.
    But it makes no sense to continue the subthread that way.


    That sometimes happens when writing replies inline, as I read the posts.
    I've tried to snip a bit more here to reduce any repetitions.



    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Rosario19@3:633/10 to All on Fri Nov 6 12:29:54 2026
    On Fri, 6 Nov 2026 00:27:15 +0000, bartc wrote:
    On 05/11/2026 22:41, Janis Papanagnou wrote:
    On 2026-11-05 13:17, bartc wrote:

    We still have the advantages that I mentioned elsethread. But that
    was not my question.

    I remind: since they have introduced op= to return a value why did
    they do it the way they did. Specifically, what are the use-cases
    that they support by their implementation semantics (with rvalues
    and right-associativity).

    Algol68 had this big idea that everything has to return some value, >including compound assignment. Given that, it made these choices, which >includes ordinary assignment having RTL precedence, and compound
    assignment being LTR.

    Personally I don't see any great need to allow compound assignments to
    be chained at all, as it only leads to harder-to-understand code with
    too many things being updated within the same statement.

    So my compound assignments return 'void'.


    But doing it with ordinary assignment has some advantages:

    * Only one assignment is done, not 4 lots of /compound/ assignments.

    This appears to me to be nonsensical given that we are talking about
    abstractions in a higher-level language, and that the transformation
    to machine instructions need not even result in different code.

    ˙˙ With a poor implementation, that will be less efficient

    With poor implementation everything will be less efficient.

    number *:= base +:= digit

    this would change base and number... for me would be ok

    I did a brief speed test using A68G. That version was 50% slower than:

    number := number * base + digit;

    this would change only number


    --- PyGate Linux v1.5.19
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)