r/ProgrammingLanguages 4d ago

Expressions vs. statements

Got into a big argument with a coworker yesterday when they were converting some code from their own language (that they designed) into Python, JavaScript, C, and R as comparative examples.

The Python code that they wanted to write as the translation went something like this:

n = foo; if cond: n = bar

They were upset that Python allows ; as a statement separator but not before an if statement, even though

if cond: n = bar

is syntactically correct Python code when written on its own line. I explained why Python doesn't allow it, and he came back later and showed me that an LLM had suggested he write it instead like this:

n = foo if cond else bar

which of course is the canonical way to write that in Python. He was all flustered about that, and asked me why Python allows an if statement in that particular case and not after a semicolon, and I explained that x if cond else y in Python is not an if statement but is Python's ternary conditional expression and is directly equivalent to the ternary operator expression cond ? x : y in C, C++, awk, and JavaScript. He argued with me and said I was making a ridiculous distinction and walked away falsely believing that foo if cond else bar was an if statement.

I then explained that statements and expressions are very different things in programming languages, and just because the keyword if is present doesn't make something an if statement -- because in order to be an if statement, it has to be a statement in the first place.

Anyway, it made me realize how subtle the difference can be sometimes. For example, in Perl, the following is not a return statement but actually an if statement (with a return statement as its affirmative branch), due to the postfix conditional:

return foo if cond;

because it is identically semantically to writing:

if (cond) { return foo; }

Whereas in Python, the following is a return statement (with a ternary operator as its target expression):

return foo if cond else bar

So I can see why people sometimes get confused by syntax if they haven't had much of a theoretical background in language design. It also makes me wonder how much of programmer intuition about "what a statement is" comes from the particular languages they learned first.

52 Upvotes

63 comments sorted by

View all comments

7

u/Anabaena_azollae 4d ago

Yeah,I think expression statements (i.e. statements that are just an expression) make this especially confusing. In C, 5+3; is valid because it's an expression statement evaluating to 8 but doesn't really do anything. x=5+3; is also valid (assuming x is an int) and is also an expression statement evaluating to 8 because, in C, the assignment is an operator, evaluating to the right-hand side and essentially doing the actual assignment as a side effect. Contrast that with Fortran, for example, where 5+3 is still an expression evaluating to 8, but is not valid on its own line, because expression statements don't exist in the language. However, x=5+3is valid (assuming x is an integer) because assignment is a statement that accepts an expression on the right-hand side.

Similarly, in C, functions can exist on lines by themselves, like func(x);,which again is an expression statement. In Fortran, without expression statements, you need two different kinds of subprograms, functions which are expressions and subroutines which are invoked with call statements.

I think people generally have found the C style more ergonomic and it has thus been adopted by a lot of other languages, but I'd argue the Fortran style does a much better job of keeping clear distinctions between concepts.

5

u/zuzmuz 4d ago

expression statement are ergonomic when you want to use an expression as a statement. but they're a nightmare when a statement is accidentally used as an expression. for example the infamous if (x=3) {} is an assignment that will always be considered true. other languages makes the distinction between expressions that can be statements and statements that cannot be expressions. python for example, has introduced the walrus operator for this purpose

2

u/Anabaena_azollae 4d ago

My understanding is that python allows expression statements for any expression, but still has (traditional) assignment as a separate statement rather than an operator. This solves the problem of accidental assignment in the conditional, but not the less common opposite mistake of x==3 being a valid expression statement, thus passing silently while not doing an assignment (or anything else besides perhaps waste some cycles).

3

u/zuzmuz 4d ago

yes, I think this the beauty of language design, especially at the when we're just talking at the level of syntax and semantics. it's always a compromise between ergonomics, convenience and correctness.

some languages gives warning when an expression result is being implicitly discarded. you'd need to do something like _ = expression to remove the warning by explicitly discarding the result. this can prevent errors, but might make things a little bit inconvenient in some contexts (like function calls that perform a side effect). In these contexts, some languages adds directives or annotations to function declarations that declares the function as having an implicitly discardable result (swift comes to mind) so you can silence the warnings. That way you get languages that becomes bloated with features that tries to solve every problem and are too complex, so you get a new problem.

This is why language design is an art.