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.)

30
  • 12
    JS and PHP do not have a large "install base"?
    – mhr
    Feb 13, 2014 at 12:33
  • 47
    @mri I suspect sarcasm.
    – Andy Hunt
    Feb 13, 2014 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. Feb 13, 2014 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, 2014 at 14:58
  • 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. Feb 13, 2014 at 18:57

9 Answers 9

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).

14
  • 5
    +1 for bringing up a = b = c which the other answers do not really bring up.
    – Leo
    Feb 13, 2014 at 15:53
  • 6
    A third resolution is to use a different symbol for assignment. Pascal uses := for assignment.
    – Brian
    Feb 13, 2014 at 19:08
  • 6
    @Brian: Indeed, as does C#. = is assignment, == is comparison. Feb 13, 2014 at 19:47
  • 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, 2014 at 22:27
  • 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, 2014 at 23:48
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.

9
  • 1
    Is the world ready for non-ASCII Unicode symbols in programming languages?
    – billpg
    Feb 14, 2014 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, 2014 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. Feb 14, 2014 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, 2014 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, 2015 at 10:49
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.

1
  • +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, 2014 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.

14
  • 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).
    – user7043
    Feb 13, 2014 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.
    – user7043
    Feb 13, 2014 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 <other>->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. Feb 13, 2014 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.
    – user7043
    Feb 13, 2014 at 15:14
  • 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, 2014 at 9:39
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.

3
  • Would that while loop condition deallocate and create a new ob object with every loop? May 3, 2017 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 4, 2017 at 8: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. Apr 15, 2021 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) ...
10
  • 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.
    – user7043
    Feb 13, 2014 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. Feb 13, 2014 at 13:05
  • if you disallow implicit conversion to bool then you don't have to worry about assignment in conditions Feb 13, 2014 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.
    – user7043
    Feb 13, 2014 at 13:10
  • @ratchetfreak you could still have an issue with assigning actual bools
    – jk.
    Feb 13, 2014 at 13:25
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

4
  • 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.
    – user7043
    Feb 13, 2014 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 Feb 13, 2014 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.
    – user7043
    Feb 13, 2014 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, 2014 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.

2
  • 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.
    – user7043
    Feb 13, 2014 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, 2014 at 3: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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.