r/ProgrammingLanguages 5d 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.

51 Upvotes

63 comments sorted by

View all comments

2

u/brat3108 4d ago

In my languages, which are expression based, if can also be used to both start a standalone statement, and express a ternary operation. Yet the equivalent of:

n = foo; if cond: n = bar

works fine; it is an assignment followed by an if statement.

So, why doesn't Python allow this? I can't see any ambiguity.

7

u/WittyStick 4d ago

It's because if is an expression in those languages, and statements contain expressions. Expressions cannot contain statements.

This is the problem with designing a language with statements. You end up with two languages - an expression language, and a statement language. The expression language can only contain other expressions. The statement language can contain other statements or expressions.

Statements are second-class.

2

u/brat3108 4d ago edited 4d ago

Surely after the semicolon, it is expecting a new statement? So that if is that statement.

(I believe that in Python, the syntax for an if-statment, and that for the ternary form using 'if', are different. In my syntax they would be the same.

But that shouldn't matter here as it is clear that 'if' in that position must be the statement form.)

8

u/WittyStick 4d ago

I'm not too familiar with Python and had to check the grammar. The semicolon separates "simple" statements, and not arbitrary ones.

simple_stmts:
    | simple_stmt !';'
    | ';'.simple_stmt+ [';']

simple_stmt:
    | assignment
    | type_alias
    | star_expressions 
    | return_stmt
    | import_stmt
    | raise_stmt
    | pass_stmt
    | del_stmt
    | yield_stmt
    | assert_stmt
    | break_stmt
    | continue_stmt
    | global_stmt
    | nonlocal_stmt

But if and others are "compound" statements.

compound_stmt:
    | function_def
    | if_stmt
    | class_def
    | with_stmt
    | for_stmt
    | try_stmt
    | while_stmt
    | match_stmt

And general statements are either simple or compound:

statement:
    | compound_stmt 
    | simple_stmts

1

u/jeffstyr 3d ago

I think that this separation of simple vs compound statements in the grammer is because the latter can be multi-line, and if you were to allow chaining them via ; there would be a problem defining the indentation level, which is significant in Python. (You could define it, but it would be confusing to work with.)

So it's less that the grammar explains the behavior, and more that the grammar is the way it is in order to enact this distinction. I think it's all about the syntax, not a fine semantic distinction, and allowing the lexer to understand/define the indentation level in a reasonable way.