top | item 32066670

Statements vs. Expressions

63 points| kretaceous | 3 years ago |joshwcomeau.com

82 comments

order
[+] beeforpork|3 years ago|reply
I am puzzled. 'statement' and 'expression' are items of JavaScript syntax (or C or Java or C++ or Perl or whatever). So to explain it, you point to the syntax definition.

This isn't going to be explained satisfactorily by analogies or intuition, and it won't be logical at all, because it isn't, and you could define the grammar with no such distinction or a different set of distinctions as many posters already showed here.

Besides being arbitrary, I also never found this a useful distinction after having programmed in languages that don't distinguish this. And look at how GCC introduced ({...}) to convert back a statement into an expression.

Anyway, to define this, you have to simply list all the things: statements are 'return', '{...}', 'while() {... }', etc., while expressions are '5', 'f(...)', etc. That's it, that's the explanation. No intuition, just a comprehensive list taken from the grammar.

If course there is some rough idea behind it, which explains why there is a difference, but this is a history class, not an explanation: statements do some action, i.e., this is an imperative concept. Expressions have a value, i.e., this is a functional concept.

[+] wruza|3 years ago|reply
No intent to sound negative, but doesn’t explanation by numerous cues mud the water even more? I bet most of students are left confused with this “wrapper statement” thing. Magical thinking induced.

If the question is how they are different, then the answer is grammatically, and then the explanation of a grammar follows.

If it is why we make such distinction, then the answer is because the language creator decided so, followed by tradeoffs and pitfalls of making a specific language construct to have a value.

[+] epolanski|3 years ago|reply
I feel the same towards the article, I think that the moment you need to resort to "think of it as..." it's the moment you lose me.

I also don't think he does a good job at explaining the biggest source of misunderstanding. Quoting the article:

`Expressions and statements are distinct things.`

That is not really correct.

A function call, e.g. is both an expression and a statement:

`foo(bar)` on it's own is both an expression (it returns a value, even if the value returned is undefined) and a statement.

In fact, determining if a piece of code is an expression or a statement in all borderline cases depends on the context.

```

const foo = function bar(){};

function bar(){}

```

On line 1 the bar function is an expression, on bar 3, is a statement.

There's nothing really about the function on it's own that can tell you whether it is an expression or a statement.

On the other hand, a nice way to differentiate statements from expressions is that you cannot use statements where you need expressions.

```

console.log(if (3 > 2) {})

const foo = while (i < 3) {} ```

both lines are syntax errors.

[+] nivertech|3 years ago|reply
Not a JS-specific answer to this question:

expression

- generally returns a value, possibly a void value, or doesn't return control at all.

- it consists out of operators, operands, and function calls

- can be composed out of one or more sub-expressions, and the order of their evaluation isn't always defined (i.e. to allow compiler optimizations).

- usually it doesn't have side-effects (i.e. pure), unless it uses random, time, IPC, system, or HW-specific pseudo-functions, pseudo-variables, or macros as a sub-expressions. Nested assignments and variable increments/decrements/etc. are also not pure.

- a result of a function call

statement

- usually an imperative command

- usually indicate a side-effect (e.g. assignment, sending a message, etc.)

- can be a procedure call or macro

- can be composed out of expressions and nested statements, but with better defined semantics / order of evaluation

- a special case: control flow statements

- a special case: "return" statement - evaluate a value and returns it as a result of the current function

[+] chrismorgan|3 years ago|reply
> statement

> - a special case: control flow statements

But it’s worth noting that this is not true in all languages: in some languages, almost everything or actually everything is an expression. In Rust, for example, control flow things may be statements or expressions (a mildly fuzzy distinction, but it comes down to whether it produces a value other than (), and whether it needs a semicolon after the closing curly brace), and some control flow things are very useful as expressions:

  let a = if b {
      c
  } else {
      d
  };

  let e = loop {
      break f;  // f is a value, not a label (which uses lifetime syntax, 'g).
  };
[+] chrismorgan|3 years ago|reply
> (5 + 1) * 2

That’s six expressions, not five. Josh conflates the the addition expression and the parenthesised expression, but they’re distinct (even if you couldn’t represent the semantics without the parenthesis due to operator precedence).

Here’s an approximate representation of the parse tree, indicating which nodes are expressions:

  MultiplicativeExpression {        ←
    ParenthesizedExpression {       ←
      AdditiveExpression {          ←
        DecimalIntegerLiteral("5")  ←
        "+"
        DecimalIntegerLiteral("1")  ←
      }
    }
    "*"
    DecimalIntegerLiteral("2")      ←
  }
[+] gumby|3 years ago|reply
One of the nice things about Lisp is there are no statements, only expressions. It's easier to think about your code.

Lisp was the first language I used (other than assembly code) so when I encountered C I was puzzled why they even bothered to have statements at all. 40 years later I still don't understand the point.

[+] S1ngleM4lt|3 years ago|reply
As someone who had recently started going the other direction, from C and C-based languages to those more influenced by lisp, it took a bit of getting used to but I’ve grown to appreciate the expressiveness and composability. The only thing I can think of that statements allow, but as I understand it pure expressions don’t, would be early returns. The closest replacement I’ve seen is to just make “return” raise an exception and wrap the whole expression with the try/catch equivalent, which isn’t terrible but was a bit surprising coming from a C-based background where returning early is a fairly common pattern.
[+] goto11|3 years ago|reply
Haskell is also based on an everything-is-an-expression syntax, but ended up re-inventing statements one level up, in the do-notation blocks.

To me, this indicates there is some merit to the statement/expression distinction.

[+] zabzonk|3 years ago|reply
Understand where you are coming from, but given the nature of C, what is the compiler to do with something like:

    1 2 3
which are three expressions, and probably the programmer meant "123". Of course:

    1; 2; 3;
is OK.
[+] submeta|3 years ago|reply
The article left me more confused than enlightened.

My understanding is this; Expressions return a value, statements on the other hand are actions, commands, definitions, declarations, assignments, and control structures.

[+] oxff|3 years ago|reply
Expressions `yield` values, statements don't yield values.

`square = square(4)` is a statement (a definition to be exact) that associates name `square` with the value `square(4)`.

The name `square` itself, when later used, is an expression, because when you call it, it evaluates to a value `square(4)`.

[+] amadeuspagel|3 years ago|reply
For example, you might have tried writing something like this:

  foo = if (x < y) {
    ...
    ...
  }
Doesn't work because statements don't yield values.
[+] usrbinbash|3 years ago|reply
I think the simplest explanation is; "An expression can be used where a value is expected".

What is sometimes confusing, is that some statements also double as expressions, that is, also return values; and that it also depends on the language. eg. the assignment statement returns a value in C and Javascript, but not in Go or Python;

    printf("%d\n", a = 42);
    // out: 42

    console.log(a = 42)
    // out: 42

    fmt.Printf("%d\n", a = 42)
    // out: syntax error: unexpected =, expecting comma or )

    print("%d" % a = 42)
    # out: SyntaxError: expression cannot contain assignment, perhaps you meant "=="?
Also depending on the language, expressions themselves may be valid statements. For example, this compiles in C;

    int main() {
        12 + 42;
    }
;this is a valid python program:

    12 + 42
;but if I try this in Go:

    package main
    func main() {
        42
    }
    // ./main.go:3:5: 12 + 42 (untyped int constant 54) is not used
[+] bluetomcat|3 years ago|reply
Generally, an expression returns a value and can be used as the operand of an operator, whereas a "statement" is the terminal "top-level" node that doesn't return a value and cannot therefore be nested.
[+] lucideer|3 years ago|reply
Probably worth adding "(javascript)" to the HN title - for non-JS see @nivertech's excellent comment here.

I may be wrong but I actually think the explanation can be a little simpler than the article posits: an expression (in JS) is something that returns a value. That's it. The value can be undefined/void, but it's still a value being returned.

One important detail the article seems to miss that I've come across whie explaining JS syntax to JSX-learners is that, while statements usually contain expressions, expressions can also contain statements. This is because a function definition returns a value (the function instance) & obviously function bodies can contain any JS syntax. This is useful if you (for some odd reason) want to embed a statement in some JSX code (since JSX only supports JS expressions).

[+] hardwaregeek|3 years ago|reply
This is why taking a basic programming language course is pretty important. I never remembered the difference until I had to represent both in an AST. You can teach it without talking about ASTs or interpreters, but it's teaching without any context or motivation, thereby making it harder to learn.

I do think mandatory semicolons make the distinction a little easier. If it has a semicolon, it's probably a statement. And for most C-style languages if it has curly braces, it's probably a statement too. Otherwise it's an expression.

[+] amadeuspagel|3 years ago|reply
> I think we often blame React for seemingly arbitrary rules, like how components must return a single top-level element. But more often than not, React is just warning us about a JavaScript limitation.

Really? With lit you can create a component that has several top-level elements[1].

EDIT: In fact this works even in react/preact with htm[2], so it's just a limitation of JSX.

[1]: https://lit.dev/playground/#project=W3sibmFtZSI6InNpbXBsZS1n...

[2]: https://github.com/developit/htm

[+] niels_bom|3 years ago|reply
As a programming teacher I think this is quite a fundamental thing to learn for new programmers. Even though lots of tutorials and books don't mention this explicitly people usually get an intuition for the difference after a while.

But in the beginning it's hard for people to grok an expression with a couple of nested expressions in it. Especially if there's some functions calls in there as well.

Axel Rauschmayer has a good page (1) on this topic as well. Quite dry, so less beginner friendly.

So what I like about Josh W Comeau's article is that it's easy to read with lots of examples and even an interactive widget.

Oh and about interactive visualization of expression evaluation: Thonny (2) does this real nicely for Python code. Excellent for beginners imo.

[1] https://2ality.com/2012/09/expressions-vs-statements.html

[2] https://thonny.org/

[+] bern4444|3 years ago|reply
Something that clicked for me recently in JS and specifically React was why we use {} for interpolation in a component. So an example:

  const MyComponent = () => {
    return (
      <>
        <h1>Hello world</h1> 
        { arr.map(val => <ListItem val={val} />) }
        <h1>Goodbye world</h1> 
      </>
    );
  }
Curly braces often denote expressions. They're everywhere:

In functions:

  const x = () => {

  };
  // or
  function myFunc() { 
  
  };
In conditionals (while the conditionals are statements their bodies are expressions)

  if (x > 2) {
    return true;
  }
In Loops

  while (x > 2) {
    doSomething();
  }
etc.

This is obviously part of the language grammar of many languages and makes it obvious why React uses curly braces as well for interpolating values.

[+] tomxor|3 years ago|reply
I feel like the examples and description for "statements" fails to demonstrate their breadth.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

In fairness it's difficult to talk about statements accurately and succinctly without being more abstract - and perhaps the more general observation here is how explanation by example alone is not always sufficient. A more general and yet tangible starting point for statements in JS could be "anything ending in a semicolon" (not quite true but almost for normal code), which at least is better at highlighting it's breadth before piling into arbitrary examples.

[+] dtagames|3 years ago|reply
The way I think of it is that statements involve keywords from the language (like 'let', 'export class', or 'if') while expressions do not.

With this definition, it's easy to see that, if the syntax starts with a language keyword, it's a statement.

BTW, language functions (like 'Array.join()') are expressions because they don't start with a keyword.

[+] ufo|3 years ago|reply
There is some historical precedent for what you said. The first programming languages were assembly languages, which only have statements that you write one per line. You can sort of say that each instruction name is a "keyword".

    mov eax ebx
    jump 1235
    xor eax eax
The first big programming language with expressions was Fortran. Its expressions were for mathematical formulas. Everything else was a statement that you still had to write one per line (and in under 80 columns, to fit in a punch card).

Part of the reason fortran was like this is that parsing algorithms were quite primitive. Ideas such as lexing, "tokens", and context-freem grammars, which are standard today, weren't common back then. For example both of the following were valid ways to start a loop in Fortran. Note the lack of whitespace in the second!

    do i = 1, 10
    doi = 1,10
Moving on, as compiler writers got better at parsing it became more popular to allow free formatting in programming languages. Instead of mandating one statement per line, use semicolons as statement terminators and let you format it as you see fit. Over time this led to a blurrying of the distinction between statement and expression. For example, in C and Javascript many things that used to be statements are now expressions (one notable example being assignments). This is taken the furthest in functional programming languages, where almost everything is an expression.
[+] simiones|3 years ago|reply
That doesn't really work, since `alert("Hello, World!")` or `doSomething()` are usually statements, not expressions; while `new SomeFunction()` is usually an expression, as is `'abc' in ['abc']`

I think that, in the context of JavaScript, if you want a "quick and dirty" way to differentiate them, the `console.log( /* put thing here */ ) ` idea from the article is the best way to go - if you can validly put the thing there, it's an expression, otherwise it's a statement.

[+] Measter|3 years ago|reply
But that logic falls apart with languages such as Rust, where a lot of expressions start with a keyword.
[+] goto11|3 years ago|reply
JavaScript have operators which are keywords, e.g. instanceof and void. "void(2 + 2)" is a valid expression
[+] photochemsyn|3 years ago|reply
I've been reading this on functional JS:

https://news.ycombinator.com/item?id=31944352

For example, functions that return functions:

const filter = (predicate, xs) => xs.filter(predicate)

So it's an assignment, leading me to think it's a statement, but then using filter later would be an expression?

[+] filoeleven|3 years ago|reply
That example is particularly confusing because it’s a function definition written as an assignment for IMO no good reason. The outcome is the same either way, but

  function filter(predicate, xs) { return xs.filter(predicate) }
lets you know immediately that “filter” is a function, not a value. There are valid cases where assigning an anonymous function a name via “const” is appropriate, for example when working with higher-order functions, but most of the time it’s not necessary or helpful. I wish it hadn’t caught on.

EDIT: I should have read your comment more carefully. The example function is not a HOF though, it runs xs.filter(pred) when called. A HOF would be

  function addConst(x) ( return (y) => x + y }
  const add3 = addConst(3)
  add3(5) // returns 8
The second line can still be written as

  function add3(x) { return addConst(3) }
I think people use the const form because there’s less text to write. Syntax highlighting makes this form more bearable, but I still don’t like it.
[+] substation13|3 years ago|reply
They should deprecate Array.prototype.forEach for muddling statements and expressions!