9
$\begingroup$

Some languages distinguish expressions from statements so that something like

int x = 2;
foo((x += 2));

is invalid. Other languages don't, so that a similar

(let ((x 2))
  (foo (incf x 2)))

is valid.

Under what criteria is it beneficial to distinguish the two, under what is it beneficial not to, and under what is it just a matter of preference?


This question is similar, but it's asking about how to specify the distinction; whereas, I'm asking about under what conditions is the distinction meaningful or useful.

$\endgroup$
3
  • $\begingroup$ @mousetail No, this is not a duplicate, it's asking about a design choice of whether to have both statements and expressions, not about what the meanings of those two words are. $\endgroup$
    – kaya3
    Commented Jun 2, 2023 at 22:51
  • 1
    $\begingroup$ It seems to me this question presupposes that such a distinction is something a language designer has control over, while it is a mental model representing the observation of properties of a given grammar. $\endgroup$
    – Longinus
    Commented Jun 4, 2023 at 2:03
  • $\begingroup$ @Longinus I agree that the distinction matters more as a mental model to a user of a language, but that doesn't mean that the designer of a language has no influence over how most developers will think of it $\endgroup$ Commented Jun 9, 2023 at 19:38

3 Answers 3

10
$\begingroup$

Particularly in the imperative paradigm, some kinds of statements don't make sense as expressions because there is no sensible value for them to have:

  • if statements, particularly when there is no else clause.
  • for and while statements.
  • return, break, continue and throw statements.
  • Function calls whose return type is void.

That's not to say that these can't be made into expressions: an if without an else could have an optional value, and it's definitely possible to have values for loop expressions (notably, in Rust a loop expression has a value given by a break <expr>; inside the loop).

Likewise, statements like return and throw do not "complete normally" so there is no need for them to have values even if they are expressions ─ but this does mean the language's type system needs to accommodate this, typically with a bottom type. Similarly, void functions can return a special unit type, but then the language needs to make void a unit type.

So it would be possible for an imperative language to forego statements and have only expressions, but this would have impacts on what kinds of loops are possible in the language, and would impose requirements on the type system. Not every language necessarily wants option types and explicit bottom and unit types.

$\endgroup$
5
  • $\begingroup$ That's interesting, I certainly would not have thought about how this affects the type system. Do you have an example of a language that doesn't have an option type? $\endgroup$ Commented Jun 2, 2023 at 22:51
  • 1
    $\begingroup$ @AmanGrewal All of the old-school imperative languages don't have option types in their standard libraries ─ C for example. Java didn't until 2014. $\endgroup$
    – kaya3
    Commented Jun 2, 2023 at 22:54
  • $\begingroup$ I can't speak for other languages, but is there a difference between "optional types" and a language (like C) that supports both null pointers and union types? $\endgroup$ Commented Jun 2, 2023 at 22:58
  • $\begingroup$ Swift's optional is a tagged union, so Optional<Int16> is 3 bytes and Optional<Int64> is 9 bytes, for example. $\endgroup$
    – Bbrk24
    Commented Jun 2, 2023 at 22:59
  • 1
    $\begingroup$ Yes, nullable types and optional types aren't the same, and also e.g. primitive int types generally can't be made nullable and have no niche for a sentinel None value in an undiscriminated union (like C's unions). $\endgroup$
    – kaya3
    Commented Jun 2, 2023 at 23:00
5
$\begingroup$

A statement is a node in a list, while an expression is a node in a tree.

This is important, because a list is isomorphic to a tree formed by pairs whose right leaves recurse.
That is, a list x, y, z, w is no different from a tree x, (y, (z, w)).

As it stands, even in "expression-oriented" languages such as Lisps or Haskell/MLs, statements can be introduced as easily as

(defun do
    [x ...xs] (ignore x (do xs...))
    [x] (return x))

or

(;): a -> b -> b
x; xs = ignore x then xs

which can be used as

(do
    (println "h")
    (println (time :UTC))
    (println 42))

and

main =
    println "h";
    println $ time UTC;
    println 42

respectively.
Both are examples of statements in languages whose grammars have nothing called "statement."

From the above, it then reasons that whether or not a language has first-class support for statements is a stylistic choice based on personal preferences.
The main two reasons for a language designer to make such a choice are

  • whether or not the language being worked on has a high number of built-in or heavily used operations which both
    • are to be used sequentially
    • and/or have no meaningful result,
  • and way more importantly, how the language designer wants her language to look like.
$\endgroup$
3
  • 1
    $\begingroup$ This is exactly how Mathematica's ; operator work. $\endgroup$
    – alephalpha
    Commented Jun 4, 2023 at 12:30
  • $\begingroup$ If ignore x means that x is discarded, and the language is lazily evaluated, then println "h"; println $ time UTC; println 42 would do nothing, right? $\endgroup$
    – Pablo H
    Commented Aug 9, 2023 at 17:53
  • $\begingroup$ @PabloH Yes. This illustration expects strict evaluation with no complex IO typesystem. Under pure lazy evaluation, you'd use CPS, Monad's >>= operator, or something else of this order. $\endgroup$
    – Longinus
    Commented Aug 9, 2023 at 18:35
3
$\begingroup$

I second kaya3's answer, and I would add one category: declarations.

As an example of a statement-only thing, think of anything where it's a pure side effect. Storing a value atomically, declaring and setting a new variable, etc. C is not expression-oriented, but it's choice to make assignment an expression was a big mistake.

Similarly, declarations serve a different purpose: registering to the compiler something that does not translate to code.

So you have:

  • Declarations: Data for the compiler.
  • Expressions: Code whose primary purpose is to produce a value.
  • Statements: Code whose primary purpose is to produce a side effect.

I am designing a language. Originally, it was going to be expression-only. I even found solutions to all of the problems in kaya3 mentioned.

Now, however, my language distinguishes between expressions, statements, and declarations.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .