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.
while((x = getValue()) != null) {}. Replacements will be uglier since you'll need to either usebreakor repeat thex = getValueassignment. – CodesInChaos Feb 13 '14 at 13:04I 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:54const 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. Writingd=(double)(float)fwould make intent clear. – supercat Feb 13 '14 at 19:27x==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:47within JS and probablydeleteas well, and knowing thatevalwas 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"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:58Debug.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===forever. – wberry Feb 13 '14 at 21:34==to represent two different equality-test operators, givenstring s1="9",s2=9.ToString(); object o1=s1;one can end up withs1==s2ands1==o1being true, buts2==o1being false. Given those definitions in the sane ("Option Strict On") dialect of VB.NET,s1 is s2ando1 is s2would be false, ands1 is o1would be true;s1=s2would be true, but neithers1=o1ands2=o1would compile. – supercat Feb 13 '14 at 21:52===in languages that have automatic conversion which I consider a misfeature. For example2 == '2'is true in JavaScript but not in Python. – wberry Feb 13 '14 at 21:56intanddouble, for example, could be expressly allowed (and defined to cast theinttodouble), butint-floatandlong-doublecomparisons should not cast the first operand to the type of the second. – supercat Feb 13 '14 at 22:03===to the language precisely to provide an alternative to that misfeature. When we implemented theswitchstatement we realized that if we wantedcase 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:092 + '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 implya is b(identity), but breaking transitivity (for either case) is a red flag. – wberry Feb 13 '14 at 22:17switchstatement 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. Hadswitchbeen 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?+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:542 == 2.0does return true even thoughintandfloatare different types in Python. If anything, JavaScript's===is closest in equivalence to Python'sis(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'sObject.equals). (Apparently the recentObject.isseems to act even more like Python'sis.) – JAB Feb 14 '14 at 17:45((2**128 / 2.0 + 1) == (2**127 + 1)) is Falseand other problems. – wberry Feb 14 '14 at 19:142**128 / 2 + 1 != 2**127 + 1as/always produces afloat, but the behavior of//is inconsistent with that as while it acts as as an integer division operator when applied toints it is a floor-division operator when applied tofloats, so2**128 // 2 + 1 == 2**127 + 1but2**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:18round(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)) == intwhiletype(round(float_value), 0) == float). – JAB Feb 14 '14 at 20:23