27

I'm developing a language which I intend to replace both Javascript and PHP. (I can't see any problem with this. It's not like either of these languages have a large install base.)

One of the things I wanted to change was to turn the assignment operator into an assignment command, removing the ability to make use of the returned value.

x=1;          /* Assignment. */
if (x==1) {}  /* Comparison. */
x==1;         /* Error or warning, I've not decided yet. */
if (x=1) {}   /* Error. */

I know that this would mean that those one-line functions that C people love so much would no longer work. I figured (with little evidence beyond my personal experience) that the vast majority of times this happened, it was really intended to be comparison operation.

Or is it? Are there any practical uses of the assignment operator's return value that could not be trivially rewritten? (For any language that has such a concept.)

billpg
  • 611
  • 12
    JS and PHP do not have a large "install base"? – mhr Feb 13 '14 at 12:33
  • 47
    @mri I suspect sarcasm. – Andy Hunt Feb 13 '14 at 12:41
  • 12
    The only useful case I can remember is while((x = getValue()) != null) {}. Replacements will be uglier since you'll need to either use break or repeat the x = getValue assignment. – CodesInChaos Feb 13 '14 at 13:04
  • 12
    @mri Ooh no, I hear those two languages are just trivial things without any significant investment at all. Once the few people who insist on using JS see my language, they will switch over to mine and never have to write === again. I'm equally sure the browser makers will immediately roll out an update that includes my language alongside JS. :) – billpg Feb 13 '14 at 14:58
  • 1
    @CodesInChaos You should make that an answer, as no-one else has (yet) mentioned that.

    I personally prefer while(true){x=getvalue(); if (x==null) break; ... } as this is (to me) much more obvious of what's going on than with an assignment hiding inside the while condition.

    – billpg Feb 13 '14 at 17:54
  • 4
    I would suggest to you that if your intention is to enhance an existing language and you intend it to be adopted widely then 100% backwards compatibility with the existing language is good. See TypeScript as an exemplar. If your intention is to provide a better alternative to an existing language then you have a much harder problem. A new language has to solve an existing realistic problem much much better than the existing language in order to pay for the cost of switching. Learning a language is an investment and it needs to pay off. – Eric Lippert Feb 13 '14 at 18:57
  • 3
    @EricLippert: Rather than 100% compatibility, I would think a better goal may be to have all code which compiles behave identically, but forbid certain dodgy constructs which are just as likely to represent undiscovered bugs as intended behavior, and which (if actually intended) can easily be corrected in a way which would compile identically in the old and new language. For example, const float f=0.1; double d=f; may be legal, but I'd say it's unlikely the programmer intended to make d equal 0.10000000149011612. Writing d=(double)(float)f would make intent clear. – supercat Feb 13 '14 at 19:27
  • I think it is a little vain to think that all browsers will automatically immediately accept your language. The internet is full of toy languages whose creator thought they were the greatest thing ever. – Tim Seguine Feb 13 '14 at 19:41
  • 1
    I don't see a reason for x==1; to be an error. If in your language, equality testing never has side effects, then it is only a no-op, and your compiler can erase it as dead code. Warning that it has no effect seems more appropriate to me. And I agree with some of the others who have suggested using := to signify assignment. – Tim Seguine Feb 13 '14 at 19:47
  • 1
    @TimSeguine You too missed the sarcasm. Don't worry, you're not alone. –  Feb 13 '14 at 19:52
  • 1
    @delnan Sarcasm has always been a bad fit for a text-based medium. Regardless of the intent, I don't think it puts the OP in a good light. – Tim Seguine Feb 13 '14 at 19:55
  • @supercat: You make a good point. Boy would it be nice to have a language called JavaScript With The Vexing Parts Taken Out. We could live without with in JS and probably delete as well, and knowing that eval was impossible would be so great. Though for a purely subtractive language you can achieve it by having the checkin system run a linter over the code and rejecting checkins which use the dodgy metaphor. – Eric Lippert Feb 13 '14 at 19:57
  • @EricLippert By using "use strict" and tools like jslint, that is essentially what one has. Or not? That's the case Douglas Crockford always seems to be making anyway. – Tim Seguine Feb 13 '14 at 19:58
  • @EricLippert: I wasn't thinking of taking out parts of the language, but rather adding features to control when various constructs are allowed or forbidden. For example, auto-boxing is useful in some contexts (arguments to Debug.Print) but just plain wrong in others (e.g. 3.Equals(3.0)). Having a compiler check method attributes to see when auto-boxing should be permitted would be a useful added feature, even if its sole effect was to forbid code which would box value types in a contexts where it would make no sense. Adding such a feature would "break compatibility" of some code... – supercat Feb 13 '14 at 20:30
  • ...whose behavior would have been meaningless but harmless without the feature, but I don't see a huge amount of benefit in having a compiler accept code which cannot plausibly have been intended as a sensible expression of its actual behavior. – supercat Feb 13 '14 at 20:38
  • No matter what happens, I applaud you for your desire to abolish === forever. – wberry Feb 13 '14 at 21:34
  • 1
    @wberry: Having more than one kind of equality operator is a good thing in languages where more than one kind of equivalence relation would make sense. In languages like C# which use == to represent two different equality-test operators, given string s1="9",s2=9.ToString(); object o1=s1; one can end up with s1==s2 and s1==o1 being true, but s2==o1 being false. Given those definitions in the sane ("Option Strict On") dialect of VB.NET, s1 is s2 and o1 is s2 would be false, and s1 is o1 would be true; s1=s2 would be true, but neither s1=o1 and s2=o1 would compile. – supercat Feb 13 '14 at 21:52
  • 1
    @supercat I guess what I mean is, I've only seen === in languages that have automatic conversion which I consider a misfeature. For example 2 == '2' is true in JavaScript but not in Python. – wberry Feb 13 '14 at 21:56
  • 1
    @wberry: The real problem there is the notion that operands to relational operators should be coerced to matching types. It would be better to define relational operators for particular combinations of operands that make sense, but forbid operations between types which each define their own comparison methods. Comparison between an int and double, for example, could be expressly allowed (and defined to cast the int to double), but int-float and long-double comparisons should not cast the first operand to the type of the second. – supercat Feb 13 '14 at 22:03
  • 1
    @wberry: Your conjecture is correct. We (by "we" I mean the Microsoft JScript team circa 1996-2001, which included me) added === to the language precisely to provide an alternative to that misfeature. When we implemented the switch statement we realized that if we wanted case 2: ... case "2": ... to be legal then it would also be nice to have an equality operator that had the same semantics as the switch's equality mechanism. – Eric Lippert Feb 13 '14 at 22:09
  • 1
    @supercat It's not just the relational operators that are guilty of silent conversion. In JavaScript, 2 + '2' returns '22'! Big no-no. As for your .NET examples, any non-transitivity in equality tests is a problem in my mind. String interning is one thing, a == b (equality) need not imply a is b (identity), but breaking transitivity (for either case) is a red flag. – wberry Feb 13 '14 at 22:17
  • @EricLippert That's a fun edge case; I guess I've been too frightened to try a multiple-type switch statement, so I've never tried it. – wberry Feb 13 '14 at 22:21
  • 1
    @wberry: You might recall that JS 1.0 did not have a switch statement at all; when we added it of course we had to consider all the possible crazy cases while still being backwards compatible with the previous version. Had switch been in the first version that might have been pressure towards a stricter type conversion discipline, but it was not to be. (And of course the meeting went like this: You want to add triple equals? REALLY? ok yeah, I guess we do need that.) – Eric Lippert Feb 13 '14 at 22:30
  • @wberry: Implicit string conversions with + are icky, but if they're only allowed when a string is explicitly expected they're not too bad. With regard to comparisons, I would posit that in a well-designed language, the only comparisons which compile but do not behave as an equivalence relation should be those involving variables which have explicitly requested IEEE floating-point semantics. Otherwise, if a comparison of things of type X's type and things of Y's type might not be an equivalence relation, X should not be directly comparable to Y. – supercat Feb 13 '14 at 22:54
  • @EricLippert "JavaScript With The Vexing Parts Taken Out" would be a good name. The big idea (not discussed here) alas, wouldn't be fixed with linting. I'd be happy to share more when its ready. – billpg Feb 14 '14 at 09:53
  • @wberry However, 2 == 2.0 does return true even though int and float are different types in Python. If anything, JavaScript's === is closest in equivalence to Python's is (you achieve similar loose equality effects with == by adding an __eq__ method to Python classes, it's just that strings and numbers in Python don't have implicit conversion built in; many object-oriented languages that don't support operator overloading usually have an explicit function pattern for that, like Java's Object.equals). (Apparently the recent Object.is seems to act even more like Python's is.) – JAB Feb 14 '14 at 17:45
  • @JAB yes, Python is also guilty and does int-float conversion automatically, leading to ((2**128 / 2.0 + 1) == (2**127 + 1)) is False and other problems. – wberry Feb 14 '14 at 19:14
  • @wberry C also produces an FP result when using division involving a float; a better example of conversion confusion would be the fact that, in Python 3, 2**128 / 2 + 1 != 2**127 + 1 as / always produces a float, but the behavior of // is inconsistent with that as while it acts as as an integer division operator when applied to ints it is a floor-division operator when applied to floats, so 2**128 // 2 + 1 == 2**127 + 1 but 2**128 // 2.0 + 1 != 2**127 + 1. (The behavior of // actually does make sense after a bit of thought, though.) – JAB Feb 14 '14 at 20:18
  • Also note the following: round(2**128 / 2.0) + 1 == round(2**128 / 2) + 1 == round(2**128 // 2.0) + 1 == 2**127 + 1, because the result of the float division is close enough to the correct result to be rounded (though to add more potential confusion, type(round(float_value)) == int while type(round(float_value), 0) == float). – JAB Feb 14 '14 at 20:23

9 Answers9

26

Technically, some syntactic sugar can be worth keeping even if it can trivially be replaced, if it improves readability of some common operation. But assignment-as-expression does not fall under that. The danger of typo-ing it in place of a comparison means it's rarely used (sometimes even prohibited by style guides) and provokes a double take whenever it is used. In other words, the readability benefits are small in number and magnitude.

A look at existing languages that do this may be worthwhile.

  • Java and C# keep assignment an expression but remove the pitfall you mention by requiring conditions to evaluate to booleans. This mostly seems to work well, though people occasionally complain that this disallows conditions like if (x) in place of if (x != null) or if (x != 0) depending on the type of x.
  • Python makes assignment a proper statement instead of an expression. Proposals for changing this occasionally reach the python-ideas mailing list, but my subjective impression is that this happens more rarely and generates less noise each time compared to other "missing" features like do-while loops, switch statements, multi-line lambdas, etc.

However, Python allows one special case, assigning to multiple names at once: a = b = c. This is considered a statement equivalent to b = c; a = b, and it's occasionally used, so it may be worth adding to your language as well (but I wouldn't sweat it, since this addition should be backwards-compatible).

  • 5
    +1 for bringing up a = b = c which the other answers do not really bring up. – Leo Feb 13 '14 at 15:53
  • 6
    A third resolution is to use a different symbol for assignment. Pascal uses := for assignment. – Brian Feb 13 '14 at 19:08
  • @Brian That (possibly) resolves the assignment-instead-of-comparison pitfall, but it still needs to be motivated. Does it have any advantages over ? Real advantages? And are those advantages worth all the costs and work of putting the feature in? –  Feb 13 '14 at 19:24
  • 6
    @Brian: Indeed, as does C#. = is assignment, == is comparison. – Marjan Venema Feb 13 '14 at 19:47
  • @MarjanVenema Either I'm missing some subtelity, or you need to read again. He's saying Pascal uses := for assignment. As you too say in your second sentence, C# does not: It uses = for assignment. –  Feb 13 '14 at 19:51
  • I don't see how a = b = c is a special case. Since = is not an expression python only evaluates c and gives it two names instead of one. It's a short-hand. In fact a = (b = c) is not legal python syntax. – Bakuriu Feb 13 '14 at 21:26
  • @Bakuriu I did not mean to imply it's special in the sense of treating assignment as an expression or something. It's special in that assignment to multiple names is explicitly prohibited, whereas other languages don't do such a thing (those that treat assignment as expression get it "for free"). –  Feb 13 '14 at 21:31
  • @MarjanVenema: I believe what he means by that is, Pascal's := operator is not only "different from the equality test operator" like C#'s is, but it is also not likely to be confused with it, which is a chronic problem in the C syntax. – Mason Wheeler Feb 13 '14 at 22:13
  • 3
    In C#, something like if (a = true) will throw a C4706 warning (The test value in a conditional expression was the result of an assignment.). GCC with C will likewise throw a warning: suggest parentheses around assignment used as truth value [-Wparentheses]. Those warnings can be silenced with an extra set of parentheses, but they are there to encourage explicitly indicating the assignment was intentional. – Bob Feb 13 '14 at 22:27
  • @Bob What point are you trying to made, and towards whom? –  Feb 13 '14 at 22:38
  • 2
    @delnan Just a somewhat generic comment, but it was sparked by "remove the pitfall you mention by requiring conditions to evaluate to booleans" - a = true does evaluate to a Boolean and is therefore not an error, but it also raises a related warning in C#. – Bob Feb 13 '14 at 23:48
  • @MasonWheeler: yeah, that thought hit me later. (I'm still a bit in shock after a highway accident). @ Delnan: my point was that C# also uses different operators for assignment and comparison, just like Delphi, but as Mason points out, the difference isn't big enough to avoid frequent confusion. – Marjan Venema Feb 14 '14 at 08:55
  • I think you meant if (x != null) and if (x != 0). – svick Feb 14 '14 at 11:35
  • @svick You're right, fixed. –  Feb 14 '14 at 13:00
11

Are there any practical uses of the assignment operator's return value that could not be trivially rewritten?

Generally speaking, no. The idea of having the value of an assignment expression be the value that was assigned means that we have an expression which may be used for both its side effect and its value, and that is considered by many to be confusing.

Common usages are typically to make expressions compact:

x = y = z;

has the semantics in C# of "convert z to the type of y, assign the converted value to y, the converted value is the value of the expression, convert that to the type of x, assign to x".

But we are already in the realm of impertative side effects in a statement context, so there's really very little compelling benefit to that over

y = z;
x = y;

Similarly with

M(x = 123);

being a shorthand for

x = 123;
M(x);

Again, in the original code we are using an expression both for its side effects and its value, and we are making a statement that has two side effects instead of one. Both are smelly; try to have one side effect per statement, and use expressions for their values, not for their side effects.

I'm developing a language which I intend to replace both Javascript and PHP.

If you really want to be bold and emphasize that assignment is a statement and not an equality, then my advice is: make it clearly an assignment statement.

let x be 1;

There, done. Or

x <-- 1;

or even better:

1 --> x;

Or even better still

1 → x;

There's absolutely no way that any of those are going to be confused with x == 1.

Eric Lippert
  • 46,189
  • 1
    Is the world ready for non-ASCII Unicode symbols in programming languages? – billpg Feb 14 '14 at 11:25
  • As much as I would love what you suggest, one of my goals is that most "well written" JavaScript can be ported over with little or no modification. – billpg Feb 14 '14 at 11:44
  • 2
    @billpg: Is the world ready? I don't know -- was the world ready for APL in 1964, decades before the invention of Unicode? Here's a program in APL that picks a random permutation of six numbers out of the first 40: x[⍋x←6?40] APL required its own special keyboard, but it was a pretty successful language. – Eric Lippert Feb 14 '14 at 15:17
  • @billpg: Macintosh Programmer's Workshop used non-ASCII symbols for things like regex tags or redirection of stderr. On the other hand, MPW had the advantage that the Macintosh made it easy to type non-ASCII characters. I must confess some puzzlement as to why the US keyboard driver doesn't provide any decent means of typing any non-ASCII characters. Not only does Alt-number entry require looking up character codes--in many applications it doesn't even work. – supercat Feb 14 '14 at 18:20
  • Hm, why would one prefer to assign "to the right" like a+b*c --> x? This looks strange to me. – Ruslan Aug 21 '15 at 10:49
  • If y in the first example, and x in the second were more complex expressions, or splitting it up into multiple expressions were otherwise inconvenient, there might possibly be a case. Of course, if they are simple single-character names, you are right that there is none. – Deduplicator Sep 18 '19 at 10:24
  • @Ruslan: I did not notice your comment until now, sorry. The reason to prefer it is because it is weird that in languages like C# today, left = right means "execute all computations and side effects of the left side but do not throw if the variable is bad, then execute all computations and side effects of the right side, then throw if the variable is bad, then do the assignment". It's a very strange ordering. left --> right by contrast is very clear. Evaluate the left, evaluate the right, throw if the variable is bad, do the assignment. Things happen left-to-right in that syntax. – Eric Lippert Sep 18 '19 at 15:22
  • @Ruslan: That's how we normally think of a variable assignment; we compute the thing we want to assign, and then we figure out where to put it. But in C#, since the rule is "evaluate left to right", you end up figuring out where it is going before you know what is going there. The x --> y syntax matches the way we think about it. If it seems weird, well, ask yourself if you had grown up with that syntax instead of the one you've got, would you still find it weird? It looks strange because it is unfamiliar. – Eric Lippert Sep 18 '19 at 15:24
  • Well, the left=right syntax seemed natural to me even when I was beginning to learn programming: in algebra, when solving an equation like LHS(x,y)=0 for x, we normally write the solution as x=f(y), not f(y)=x, and then, when making a program to calculate x, it's natural to continue with the same notation. I guess that's how FORTRAN was designed. The left → right syntax only becomes understandable when I consider electrical engineering schematics, where signal propagates from the left to the right side of the schematic, but this is quite far from imperative programming. – Ruslan Sep 18 '19 at 15:41
9

Many languages do choose the route of making assignment a statement rather than an expression, including Python:

foo = 42 # works
if foo = 42: print "hi" # dies
bar(foo = 42) # keyword arg

and Golang:

var foo int
foo = 42 # works
if foo = 42 { fmt.Printn("hi") } # dies

Other languages don't have assignment, but rather scoped bindings, e.g. OCaml:

let foo = 42 in
  if foo = 42 then
    print_string "hi"

However, let is an expression itself.

The advantage of allowing assignment is that we can directly check the return value of a function inside the conditional, e.g. in this Perl snippet:

if (my $result = some_computation()) {
  say "We succeeded, and the result is $result";
}
else {
  warn "Failed with $result";
}

Perl additionally scopes the declaration to that conditional only, which makes it very useful. It will also warn if you assign inside a conditional without declaring a new variable there – if ($foo = $bar) will warn, if (my $foo = $bar) will not.

Making the assignment in another statement is usually sufficient, but can bring scoping problems:

my $result = some_computation()
if ($result) {
  say "We succeeded, and the result is $result";
}
else {
  warn "Failed with $result";
}
# $result is still visible here - eek!

Golang heavily relies on return values for error checking. It therefore allows a conditional to take an initialization statement:

if result, err := some_computation(); err != nil {
  fmt.Printf("Failed with %d", result)
}
fmt.Printf("We succeeded, and the result is %d\n", result)

Other languages use a type system to disallow non-boolean expressions inside a conditional:

int foo;
if (foo = bar()) // Java does not like this

Of course that fails when using a function that returns a boolean.

We now have seen different mechanisms to defend against accidental assignment:

  • Disallow assignment as an expression
  • Use static type checking
  • Assignment doesn't exist, we only have let bindings
  • Allow an initialization statement, disallow assignment otherwise
  • Disallow assignment inside a conditional without declaration

I've ranked them in order of ascending preference – assignments inside expressions can be useful (and it's simple to circumvent Python's problems by having an explicit declaration syntax, and a different named argument syntax). But it's ok to disallow them, as there are many other options to the same effect.

Bug-free code is more important than terse code.

amon
  • 134,135
  • +1 for "Disallow assignment as an expression". The use-cases for assignment-as-an-expression don't justify the potential for bugs and readability issues. – poke Feb 14 '14 at 17:00
7

You said "I figured (with little evidence beyond my personal experience) that the vast majority of times this happened, it was really intended to be comparison operation."

Why not FIX THE PROBLEM?

Instead of = for assignment and == for equality test, why not use := for assignment and = (or even ==) for equality?

Observe:

if (a=foo(bar)) {}  // obviously equality
if (a := foo(bar)) { do something with a } // obviously assignment

If you want to make it harder for the programmer to mistake assignment for equality, then make it harder.

At the same time, if you REALLY wanted to fix the problem, you would remove the C crock that claimed booleans were just integers with predefined symbolic sugar names. Make them a different type altogether. Then, instead of saying

int a = some_value();
if (a) {}

you force the programmer to write:

int a = some_value();
if (a /= 0) {} // Note that /= means 'not equal'.  This is your Ada lesson for today.

The fact is that assignment-as-an-operator is a very useful construct. We didn't eliminate razor blades because some people cut themselves. Instead, King Gillette invented the safety razor.

  • 2
    (1) := for assignment and = for equality might fix this problem, but at the cost of alienating every programmer who didn't grow up using a small set of non-mainstream languages. (2) Types other than bools being allows in conditions isn't always due to mixing up bools and integers, it's sufficient to give a true/false interpretation to other types. Newer language that aren't afraid to deviate from C have done so for many types other than integers (e.g. Python considers empty collections false). –  Feb 13 '14 at 13:38
  • 1
    And regarding razor blades: Those serve a use case that necessitates sharpness. On the other hand, I'm not convinced programming well requires assigning to variables in the middle of an expression evaluation. If there was a simple, low-tech, safe and cost efficient way to make body hair disappear without sharp edges, I'm sure razor blades would have been displaced or at least made much more rare. –  Feb 13 '14 at 13:40
  • 1
    @delnan: A wise man once said "Make it as simple as possible, but no simpler." If your objective is to eliminate the vast majority of a=b vs. a==b errors, restricting the domain of conditional tests to booleans and eliminating the default type conversion rules for ->boolean gets you just about all the way there. At that point, if(a=b){} is only syntactically legal if a and b are both boolean and a is a legal lvalue. – John R. Strohm Feb 13 '14 at 15:07
  • Making assignment a statement is at least as simple as -- arguably even simpler than -- the changes you propose, and achieves at least as much -- arguably even more (doesn't even permit if (a = b) for lvalue a, boolean a, b). In a language without static typing, it also gives much better error messages (at parse time vs. run time). In addition, preventing "a=b vs. a==b errors" may not be the only relevant objective. For example, I'd also like to permit code like if items: to mean if len(items) != 0, and that I'd have to give up to restrict conditions to booleans. –  Feb 13 '14 at 15:14
  • @delnan, killing assignment-as-operator to cure = vs == is akin to using a tactical nuclear weapon to kill one cockroach. It is overkill. – John R. Strohm Feb 13 '14 at 16:22
  • I don't know any good use for assignment that isn't a statement, therefore I prefer to think of it as simply the most logical taxonomy. Why make it an expression when it's virtually always used as a statement? Language design doesn't start from an existing language and considers every change from the base language an action to be justified. Every feature, whether it existed before or not, needs to pull its own weight. A capability that serves no good use and introduces some pitfalls is not worth adding. But even in the aforementioned view, "tactical nuclear weapon" would be hyperbole. –  Feb 13 '14 at 16:47
  • @delnan: How about having an assignment statement which uses =, and accepts multiple =-separated lvalues of matching types, while requiring another token like := only for those rare assignments which don't fit that pattern? The only code that would require changing would be the rare code which needs to do assignments within an expression, and the use of special syntax would make clear that the assignment was indeed what the programmer intended. – supercat Feb 13 '14 at 19:11
  • @supercat As I said, I'm not convinced the latter is worth adding in the first place, regardless of how it is spelled specifically. More specifically, I don't believe it outweighs the -100 points that every feature starts out with. –  Feb 13 '14 at 19:22
  • @delnan: If one is designing a language which is similar to an existing one, such a feature may greatly ease the porting of code which makes use of the assignment-as-expression syntax. – supercat Feb 13 '14 at 19:26
  • @supercat But your proposal does not ease porting significantly, relative to mine. Both approaches make the rare assignments expressions illegal. With a separate :=, those rare assignments in expression contexts needs to be identified and changed to use a different operator. With only =, those rare assignments in expression contexts need to be identified and changed to use a separate statements. That's slightly more work, but at best +5 points, not nearly enough to pull it out of the -100 hole. –  Feb 13 '14 at 19:50
  • @delnan: Often when programmers perform an assignment within a nested expression, they do so because there isn't a clean way to pull the assignment out to a separate statements. Finding the places where = would have to become := is easy--see where the compiler squawks, and check to make sure the usage of = for assignment is deliberate. Rewriting the assignment as a separate statement in cases where the original programmer couldn't see a clean way to do so is apt to be much harder. – supercat Feb 13 '14 at 20:43
  • 1
    @delnan Pascal is a non-mainstream language? Millions of people learned programming using Pascal (and/or Modula, which derives from Pascal). And Delphi is still commonly used in many countries (maybe not so much in yours). – jwenting Feb 14 '14 at 09:39
  • Back in the days, I first learned programming in Pascal and then I moved on to C++. I remember that one of the first impressions of C++ to this naive beginner was: "Why are they using = and == instead of := and =? Isn't it kind of stupid to use the same symbol for two different meanings?" I think people with little or no programming experience may be able to see such things in a clearer light, than hardened veteran programmers do. –  Feb 14 '14 at 10:58
  • @delnan, I got my start in this crazy racket in 1970. I've been getting paid to do this, one way or another, since 1974. In all that time, I don't think I've EVER seen code actually PORTED from one language to another. (I've seen LOTS of FORTRAN-to-FORTRAN conversions, moving something from one system to another. I've NEVER seen a FORTRAN-to-PASCAL or FORTRAN-to-C or PASCAL-to-C or BLISS-to-C, or anything else. Normally, the code is rewritten from scratch at that point, with the existing code as a guide. (I've seen ONE (1) MATLAB-to-C conversion, for a radar project. Special case.) – John R. Strohm Feb 16 '14 at 07:43
5

To actually answer the question, yes there are numerous uses of this although they are slightly niche.

For example in Java:

while ((Object ob = x.next()) != null) {
    // This will loop through calling next() until it returns null
    // The value of the returned object is available as ob within the loop
}

The alternative without using the embedded assignment requires the ob defined outside the scope of the loop and two separate code locations that call x.next().

It's already been mentioned that you can assign multiple variables in one step.

x = y = z = 3;

This sort of thing is the most common use, but creative programmers will always come up with more.

Tim B
  • 461
  • 2
  • 9
  • Would that while loop condition deallocate and create a new ob object with every loop? – user3932000 May 03 '17 at 22:33
  • @user3932000 In that case probably not, usually x.next() is iterating over something. It is certainly possible that it could though. – Tim B May 04 '17 at 08:18
  • I can't get the above to compile unless I declare the variable beforehand and remove the declaration from inside. It says Object cannot be resolved to a variable. – William Jarvis Apr 15 '21 at 16:42
1

Since you get to make up all the rules, why now allow assignment to turn a value, and simply not allow assignments inside conditional steps? This gives you the syntactic sugar to make initializations easy, while still preventing a common coding mistake.

In other words, make this legal:

a=b=c=0;

But make this illegal:

if (a=b) ...
Bryan Oakley
  • 25,332
  • 2
    That seems like a rather ad-hoc rule. Making assignment a statement and extending it to allow a = b = c seems more orthogonal, and easier to implement too. These two approach disagree about assignment in expressions (a + (b = c)), but you haven't taken sides on those so I assume they don't matter. –  Feb 13 '14 at 13:01
  • "easy to implement" shouldn't be much of a consideration. You are defining a user interface -- put the needs of the users first. You simply need to ask yourself whether this behavior helps or hinders the user. – Bryan Oakley Feb 13 '14 at 13:05
  • if you disallow implicit conversion to bool then you don't have to worry about assignment in conditions – ratchet freak Feb 13 '14 at 13:08
  • Easier to implement was only one of my arguments. What about the rest? From the UI angle, I might add that IMHO incoherent design and ad-hoc exceptions generally hinders the user in grokking and internalising the rules. –  Feb 13 '14 at 13:10
  • @ratchetfreak you could still have an issue with assigning actual bools – jk. Feb 13 '14 at 13:25
  • @delnan: you are absolutely right. There are lots of factors that go into ease of use. You have to consider the tradeoffs. – Bryan Oakley Feb 13 '14 at 13:38
  • using == in a conditional is a smell, and warnings will do for the other option – ratchet freak Feb 13 '14 at 13:43
  • @ratchetfreak Surely you mean =? And although I don't know the reasons you consider it bad style, I'd guess most of the reasons apply just as well to assignment in arbitrary expressions. –  Feb 13 '14 at 13:50
  • @delnan if(value==true) is bad style, and comparing 2 booleans directly is rare enough – ratchet freak Feb 13 '14 at 14:07
  • @ratchetfreak Excuse me? The equality comparison can and most often is used for types other than booleans. Even if you prohibit conditions that don't evaluate to booleans, a == b evaluates to a boolean for practically any a, b in practically any language. –  Feb 13 '14 at 14:09
0

By the sounds of it, you are on the path of creating a fairly strict language.

With that in mind, forcing people to write:

a=c;
b=c;

instead of:

a=b=c;

might seem an improvement to prevent people from doing:

if (a=b) {

when they meant to do:

if (a==b) {

but in the end, this kind of errors are easy to detect and warn about whether or not they are legal code.

However, there are situations where doing:

a=c;
b=c;

does not mean that

if (a==b) {

will be true.

If c is actually a function c() then it could return different results each time it is called. (it might also be computationally expensive too...)

Likewise if c is a pointer to memory mapped hardware, then

a=*c;
b=*c;

are both likely to be different, and also may also have electronic effects on the hardware on each read.

There are plenty of other permutations with hardware where you need to be precise about what memory addresses are read from, written to and under specific timing constraints, where doing multiple assignments on the same line is quick, simple and obvious, without the timing risks that temporary variables introduce

Michael Shaw
  • 9,935
  • 1
  • 24
  • 36
  • 4
    The equivalent to a = b = c isn't a = c; b = c, it's b = c; a = b. This avoids duplication of side effects and also keeps the modification of a and b in the same order. Also, all these hardware-related arguments are kind of stupid: Most languages are not system languages and are neither designed to solve these problems nor are they being used in situations where these problems occur. This goes doubly for a language that attempts to displace JavaScript and/or PHP. –  Feb 13 '14 at 13:04
  • delnan, the issue wasn't are these contrived examples, they are. The point still stands that they show the kinds of places where writing a=b=c is common, and in the hardware case, considered good practice, as the OP asked for. I'm sure they will be able to consider their relevance to their expected environment – Michael Shaw Feb 13 '14 at 13:51
  • Looking back, my problem with this answer is not primarily that it focuses on system programming use cases (though that would be bad enough, the way it's written), but that it rests on assuming an incorrect rewriting. The examples aren't examples of places where a=b=c is common/useful, they are examples of places where order and number of side effects must be taken care of. That is entirely independent. Rewrite the chained assignment correctly and both variants are equally correct. –  Feb 13 '14 at 13:58
  • @delnan: The rvalue is converted to the type of b in one temp, and that is converted to the type of a in another temp. The relative timing of when those values are actually stored is unspecified. From a language-design perspective, I would think it reasonable to require that all lvalues in a multiple-assignment statement have matching type, and possibly to require as well that none of them be volatile. – supercat Feb 13 '14 at 19:17
0

The greatest benefit to my mind of having assignment be an expression is that it allows your grammar to be simpler if one of your goals is that "everything is an expression"--a goal of LISP in particular.

Python does not have this; it has expressions and statements, assignment being a statement. But because Python defines a lambda form as being a single parameterized expression, that means you can't assign variables inside a lambda. This is inconvenient at times, but not a critical issue, and it's about the only downside in my experience to having assignment be a statement in Python.

One way to allow assignment, or rather the effect of assignment, to be an expression without introducing the potential for if(x=1) accidents that C has is to use a LISP-like let construct, such as (let ((x 2) (y 3)) (+ x y)) which might in your language evaluate as 5. Using let this way need not technically be assignment at all in your language, if you define let as creating a lexical scope. Defined that way, a let construct could be compiled the same way as constructing and calling a nested closure function with arguments.

On the other hand, if you are simply concerned with the if(x=1) case, but want assignment to be an expression as in C, maybe just choosing different tokens will suffice. Assignment: x := 1 or x <- 1. Comparison: x == 1. Syntax error: x = 1.

wberry
  • 481
  • 1
    let differs from assignment in more ways than technically introducing a new variable in a new scope. For starters, it has no effect on code outside the let's body, and therefore requires nesting all code (what should use the variable) further, a significant downside in assignment-heavy code. If one was to go down that route, set! would be the better Lisp analogue - completely unlike comparison, yet not requiring nesting or a new scope. –  Feb 13 '14 at 21:36
  • @delnan: I'd like to see a combination declare-and-assign syntax which would prohibit reassignment but would allow redeclaration, subject to the rules that (1) redeclaration would only be legal for declare-and-assign identifiers, and (2) redeclaration would "undeclare" a variable in all enclosing scopes. Thus, the value of any valid identifier would be whatever was assigned in the previous declaration of that name. That would seem a little nicer than having to add scoping blocks for variables that are only used for a few lines, or having to formulate new names for each temp variable. – supercat Feb 14 '14 at 03:08
0

I know that this would mean that those one-line functions that C people love so much would no longer work. I figured (with little evidence beyond my personal experience) that the vast majority of times this happened, it was really intended to be comparison operation.

Indeed. This is nothing new, all the safe subsets of the C language have already made this conclusion.

MISRA-C, CERT-C and so on all ban assignment inside conditions, simply because it is dangerous.

There exists no case where code relying on assignment inside conditions cannot be rewritten.


Furthermore, such standards also warns against writing code that relies on the order of evaluation. Multiple assignments on one single row x=y=z; is such a case. If a row with multiple assignments contains side effects (calling functions, accessing volatile variables etc), you cannot know which side effect that will occur first.

There are no sequence points between the evaluation of the operands. So we cannot know whether the subexpression y gets evaluated before or after z: it is unspecified behavior in C. Thus such code is potentially unreliable, non-portable and non-conformant to the mentioned safe subsets of C.

The solution would have been to replace the code with y=z; x=y;. This adds a sequence point and guarantees the order of evaluation.


So based on all the problems this caused in C, any modern language would do well to both ban assignment inside conditions, as well as multiple assignments on one single row.