<p>An expression-oriented language sounds appealing, as I could have code logic inline in expressions and initializers. An <code>if</code> statement could just return the last expressions in their blocks similar to in Rust I believe:</p>
<pre><code>x = if condition {y} else {z}
</code></pre>
<p>A switch could similarly return the last expression in their <code>case</code> blocks.</p>
<p>However, what could a loop return in a way that would be useful?</p>

<expressions><loops><for-loops>
What would a for loop return in an expression oriented language?
CC BY-SA 4.0
2023-06-03T05:16:47.873Z
Mathematica is expression-oriented but dynamically typed. Loops in Mathematica simply returns `None`, unless you `Break` with some value.
alephalpha
<p>One option is outputting the loop guard. This has the advantage of always having a value even if the loop is run 0 times, but somewhat restricts how the initialization of the loop could work.</p>
<p>For example, consider code like this:</p>
<pre><code>let i = 0;
let d = for (b=1; i&lt;10; i++) {
    b*=2;
}
</code></pre>
<p>Here, it <code>d</code> will equal 1024, the final value of b. Note we type <code>b=1</code> not <code>let b=1</code> since the variables declared in that place have special meaning and that position in the loop should not be confused with a normal statement.</p>
<p>It could even work with normal variables if your language supports tuples:</p>
<pre><code>let x,y = for (b=1, i=0; i&lt;10; i++) {
    b*=2;
}
// x=1024, y=10
</code></pre>
<p>The advantages of this method are:</p>
<ul>
<li>Always produces a value even if the loop terminates after 0 iterators</li>
<li>Outputting the final value after repeated operation is potentially quite useful</li>
</ul>
<p>Disadvantages:</p>
<ul>
<li>Restricts types of operations that can be performed in the left most expression of a for loop</li>
<li>Requires declaring variables before the loop if you don't want them included in the output, polluting scope.</li>
</ul>

CC BY-SA 4.0
2023-06-03T05:27:09.527Z
2023-06-03T05:27:09.527Z
mousetail 'he-him'
CC BY-SA 4.0
2023-06-03T13:30:08.030Z
If the loop's value is always equal to the value of a specially nominated variable (`b` in this example), does it need to be the loop's value? You could equally write the same loop followed by `let d = b;`. I suppose there is the opportunity for this to be a shorthand when a loop appears in other places, but it's a non-obvious shorthand to me at least.
kaya3
CC BY-SA 4.0
2023-06-04T14:25:36.973Z
@kaya3 True, I think this idea might be even more terrible than I thought
mousetail 'he-him'
CC BY-SA 4.0
2023-06-04T17:21:02.477Z
It's not terrible! Just maybe it needs a syntax that makes clearer what value is produced.
kaya3
<p>I like the answer from kaya3, but I think there's a better option.</p>
<p>Treat the body of the <code>for</code> loop as an expression itself, and treat every iteration as evaluating that expression. Then put the results in a list and return <em>that list</em> as the result of the loop.</p>
<p>In other words, this would be perfectly valid:</p>
<pre><code>squares: []int =
for (i = 1; i &lt;= 10; i += 1)
{
    i * i;
};
</code></pre>
<p>(I apologize if this isn't readable. This code is using the syntax of my own language.)</p>
<p>After this code, <code>squares</code> would be a list of the squares up to 100.</p>
<p>This has a few advantages.</p>
<ul>
<li>After the loop, you know how many times the loop executed because that's the length of the list.</li>
<li>The only special case you need to worry about is an empty list, but that's a typical case for lists anyway.</li>
<li>Finally, you wouldn't have to differentiate between iterations that ended normally, used <code>continue</code>, or used <code>break</code>; they all can return a value and have those values added to the list.</li>
</ul>
<p>My language does this. In fact, it does this for every kind of loop, which makes things really convenient.</p>
<p>Say you have a list of users, and you want a list of just their favorite things. You would write this:</p>
<pre><code>faves: []Favorite =
foreach (user: users)
{
    user.favorite_thing;
}
</code></pre>
<p>And then I don't feel so bad!</p>

CC BY-SA 4.0
2023-06-03T01:27:21.387Z
2023-06-03T01:27:21.387Z
Gavin D. Howard
CC BY-SA 4.0
2023-06-03T01:35:05.093Z
That sounds like `.map` with extra steps.
Bbrk24
CC BY-SA 4.0
2023-06-03T01:41:53.057Z
It is, and that's where the idea came from. My examples don't show this, but sometimes, it's more readable to have a complicated loop with a body of code instead of a complicated lambda. And `map` would make it complicated to do an early `break`, for example.
Gavin D. Howard
CC BY-SA 4.0
2023-06-03T01:51:18.613Z
Early `break` could be done by running until some sentinel value (like `nil`), or by passing a `ref bool` to the callback.
Bbrk24
CC BY-SA 4.0
2023-06-03T01:56:27.740Z
And then running `filter`. What I meant by early `break` was to break out before all items were processed, something `map` does not do by itself. Also, doing that sort of thing in `map` then `filter` would require repeating the break condition. `map` is great, but sometimes a loop is better.
Gavin D. Howard
CC BY-SA 4.0
2023-06-03T02:08:43.410Z
I was thinking something more like [this](https://swiftfiddle.com/stk74cuemzfw5hdsm7q6q3kk6q).
Bbrk24
CC BY-SA 4.0
2023-06-03T02:18:09.120Z
I did not know that Swift had that. Nevertheless, I am confident that some cases will be better written as a loop.
Gavin D. Howard
CC BY-SA 4.0
2023-06-03T13:08:59.837Z
That could become a devastating memory sinkhole very easily, though. For instance, an event loop, intended to run for as long as the program is, would consume more and more memory over time, eventually resulting in an out-of-memory crash that wouldn't be identifiable at compile time or with unit tests. I'd say this would be better off as an explicit opt-in feature
abel1502
CC BY-SA 4.0
2023-06-03T13:47:47.353Z
@abel1502 in my language, if a loop body returns `void`, the compiler skips generating the coffee that would generate the list. Most uses of a loop as a statement, such as an event loop, fall under this and thus, do not become memory sinkholes.
Gavin D. Howard
CC BY-SA 4.0
2023-06-04T11:59:50.150Z
This is what list comprehensions are, e.g. `[i * i for i in range(1, 11)]` in Python. But most of the time you need a loop, you don't also need to build a list; and building a list isn't free. Perhaps a clever compiler can eliminate the list-building when it can prove the list isn't used, but that's a lot of extra work if you want to handle cases like just accessing the list's length, or just accessing its last element. So this is a useful feature for a language to have but I don't see how it's better to make all loops work like this.
kaya3
CC BY-SA 4.0
2023-06-04T12:11:41.960Z
Doing this for every kind of loop also seems very limiting ─ server applications tend to have long-running loops, combinatorial search algorithms may have loops which iterate millions or billions of times, games have loops which iterate 60 times per second ─ even ignoring the time it takes to push things to a list, this is practically a memory leak. Sure, you can have a carve-out for loop bodies of type `void`, but this means that a missing semicolon can blow up a program if you leave it running long enough. I think the answer would be improved by discussing some of these limitations.
kaya3
CC BY-SA 4.0
2023-06-04T12:16:44.670Z
Actually, going by your example syntax, even a semicolon seems not to be enough, otherwise `i * i;` would be of type `void` rather than `int`. So a loop like `for(...) { foobar(); }` will build a list if `foobar()` isn't of type `void`, even if the user only called it for its side-effects.
kaya3
CC BY-SA 4.0
2023-06-04T14:37:34.483Z
My language uses semicolons everywhere, unlike Rust. Anyway, it also errors on unused results. If you have a non-void loop that you don't assign to anything, it will error. So every loop that is not void will be easily seen.
Gavin D. Howard
CC BY-SA 4.0
2023-06-04T14:40:38.653Z
Also, list comprehensions can easily become non-readable. And having this means I don't need special syntax for that.
Gavin D. Howard
CC BY-SA 4.0
2023-06-04T15:06:31.450Z
And before you ask, yes, I know that an unused result as the last expression in a block will not be "unused" since it will make the block return that value. However, that result will propagate up to the top-level scope, where it will be unused. As long as the compiler can trace the entire propagation, the error message will be useful, and the user can figure out where they missed something. Other than that, unused results are trivially detectable at the spot.
Gavin D. Howard
<p>A common approach at stateful loops in languages with expression-oriented syntaxes is to see them as conditionals where the loop either stops itself with a result, or invokes an <code>else</code> branch.</p>
<p>Hence,</p>
<pre class="lang-fs prettyprint-override"><code>let x = while y do z else w
</code></pre>
<p>is interpreted as</p>
<pre class="lang-fs prettyprint-override"><code>let x =
    let _tmp = None
        rec _f() =
            if ((_tmp is None) and y) then (z; _f())
            else ()
        _ = _f()
        in
    if _tmp then _tmp
    else w
</code></pre>
<p>where <code>_tmp</code> holds the eventual result (in the below example set by <code>break</code>).</p>
<p>An actual, useful, example may look like this:</p>
<pre class="lang-fs prettyprint-override"><code>find[A](x: A, xs: Iterable(A)): Maybe(Index(xs)) =
    let i = 0 in
    foreach e in xs do
        if e == x then
            break i
        else
            i += 1
    else
        None

test &quot;loop&quot; with
    assert(find(5, 0.toIncluding(10)) == 5);
    assert(find(5, positiveEvenIntegrals.toIncluding(10)) == None)
</code></pre>
<p>Zig is a precedent there. Python and Ruby both have related features but lacking the returned value aspect.</p>

CC BY-SA 4.0
2023-06-04T00:41:01.870Z
2023-06-04T01:15:41.393Z
2023-06-04T01:15:41.393Z
Longinus
<h1>The setup</h1>
<p>I'll take a different approach than the other answers.</p>
<p>What if we actually allow statements to be a <em>new kind</em> of expression, different from the others? What if that new kind of expression even had its own operations? Let's find out!</p>
<p>Let's say that in our language we actually &quot;embed&quot; the statements into the expressions like this.</p>
<p>A statement can be thought of as an &quot;action.&quot; Two actions can be combined to form another action. For example, if you have an action <code>x</code> and an action <code>y</code>, you can use a sequencing operator (call it <code>;;</code>) to combine them into a new action. This new action will first do the <code>x</code> action and then do the <code>y</code> action.</p>
<p>Say we want to print <code>1</code> and then print <code>2</code>. These are two actions. You might write this <code>print(1) ;; print(2)</code>. Of course, you can come up with much better syntax, this is just for illustration. <code>;;</code> is a binary operation that takes two actions and gives you back another action (the &quot;combination&quot; of the two argument actions).</p>
<h1>While loops</h1>
<p>Alright, now what is a loop in this language? Well, let's start with a <code>while</code> loop. We can actually think of a <code>while</code> loop as a <em>function</em> that takes a Boolean expression (the conditional) and an action (the body) and returns an action. The action it returns is the action that repeats the while loop body action while the conditional is true.</p>
<p><code>while</code> actually doesn't need to be a keyword here and, in fact, there are ways you could implement it <em>inside</em> standard library of the language.</p>
<p>You could write it like this (again using a made-up syntax purely for illustration; there are much better ones):</p>
<pre><code>while(x &gt; 5,
  (print(x) ;; x := x-1))
</code></pre>
<p>It looks like I've just written a while loop in a super weird way. And that is technically true. However, we've also gained something new. Actions are now <em>first-class values</em> in our language!</p>
<p>We can actually pass around the loop body I've written above. Say you want to abstract the notion of &quot;looping 15 times.&quot; You can write a function</p>
<pre><code>repeat15 =
  fun(a) =&gt;
    let i := 0
    in
    while(i &lt; 15, (a ;; i := i+1))
</code></pre>
<p>Now you could write</p>
<pre><code>repeat15(print(&quot;*&quot;))
</code></pre>
<p>and you'll print out 15 <code>*</code>s.</p>
<p>Also, if we wanted to, we could distinguish actions from &quot;regular values&quot; (like integers or strings) at a type level. We can do this by creating an <code>Action&lt;T&gt;</code> type for actions that produce a value of type <code>T</code>. We can distinguish between a string value we already have in our hand from an action that <em>produces</em> a string value. And, furthermore, we can pass <em>both</em> of those kinds of things around at will.</p>
<p>But what is the difference between an <code>Action&lt;Int&gt;</code> and an <code>Int</code>? Well, it's the exact same as the difference between <code>/bin/ls</code> and a list of files (paraphrasing a quote from Shachaf).</p>
<h1>For loops</h1>
<p>A <code>for</code> loop (or a <code>foreach</code> loop) can be thought of as a function that takes in a list and a &quot;parameterized action&quot; (a function that gives you back an action). It returns an action that does this: it will take each item of the list in turn and give it to the &quot;parametrized action.&quot;</p>
<p>For instance,</p>
<pre><code>for([1,2,3,4], fun(x) =&gt; print(x))
</code></pre>
<p>would print <code>1 2 3 4</code> (writing spaces for newlines).</p>
<p>So, what does a <code>for</code> loop return here? It returns an action! It doesn't return any particular value produced by the loop. It returns an action that actually <em>performs</em> the loop itself.</p>
<p>I mentioned that we can also have a version of <code>;;</code> that passes a result from the first action to the second. Here's what that could look like. Call that operator <code>--&gt;</code>:</p>
<pre><code>getLine() --&gt; (fun(x) =&gt; print(length(x)))
</code></pre>
<p>This would read in a line from standard input and print its length.</p>
<p>Note that <code>;;</code> is just a special case of this:</p>
<pre><code>a ;; b
</code></pre>
<p>is the same as</p>
<pre><code>a --&gt; (fun(_) =&gt; b)
</code></pre>
<p>As I've mentioned, you can come up with a better syntax. In fact, there already are better syntaxes. The reason is that these ideas already exist.</p>
<p>And that leads me directly to...</p>
<h1>The punchline</h1>
<p>What I've just described is a particular case of the concept of a monad (in the form in which it appears in programming languages).</p>
<p>Now, there is some debate about how people should learn about monads. Ultimately, I can only provide part of the answer for this.</p>
<p>I've only described part of <em>one</em> specific monad here. I believe to learn them properly you must experiment with a variety of them. For example, look at the list monad, the reader monad and the proxy monad. There are plenty more, but that is some variety to start with. Think about how the &quot;bind&quot; operation works for each of them works, in each particular case.</p>
<p>In the context of this question, the list monad in particular would be useful to look at. Also, my answer to a question <a href="https://stackoverflow.com/questions/23188645/non-monadic-error-handling-in-haskell/23207315#23207315">here</a> talks about the <code>Maybe</code> and <code>Either</code> monads in a somewhat similar style to this answer.</p>
<p>I also did not describe the mathematical laws they must obey (though there is a relatively intuitive way to look at these laws in terms of actions).</p>
<p>I've only scratched the surface of a particular monad here, but hopefully this answer can provide some useful ideas.</p>
<p>The particular monad I've described is roughly like Haskell's <code>IO</code> monad, though I've left all calls to <code>writeIORef</code>, <code>readIORef</code> and <code>newIORef</code> implicit. Also, the <code>let-in</code> in the <code>repeat15</code> should really be a call to <code>--&gt;</code>. I've simplified that for the purposes of demonstration. There are many other monads, as well. It remains the case that <code>--&gt;</code> is the basic &quot;action combining&quot; operation. In Haskell, this is called <code>&gt;&gt;=</code>. Likewise, the Haskell name for <code>;;</code> is <code>&gt;&gt;</code>. There's also an operation that will take a &quot;regular value&quot; and give you an action that simply produces that value without doing anything else. This is sometimes called (in Haskell) <code>pure</code> or (confusingly) <code>return</code>. It's also sometimes called &quot;unit.&quot;</p>

CC BY-SA 4.0
2023-06-06T03:03:05.163Z
2023-06-06T04:31:24.227Z
2023-06-06T04:31:24.227Z
David Young
CC BY-SA 4.0
2023-06-06T03:07:28.503Z
Welcome to PLDI! This is a great answer, though I guess it does create a bit of a chicken-and-egg if I'm reading it right...what's the `T` in the `Action<T>` for a `for` loop? :p
rydwolf
CC BY-SA 4.0
2023-06-06T03:13:28.073Z
@RydwolfPrograms Good question! In that case, it would be the "unit type." This is a type that only has a *single* value. In Haskell this is called `()` and in Standard ML it is called `unit`. So, in our sort of made up syntax, let's call this type `Unit` and we could write `Action<Unit>`. Let's use the name `tt` for the only value of type `Unit`. Now, let's contrast this with `Bool`. If you have a variable `x` of type `Bool`, you know it must either be `true` or `false`. On the other hand, if `y` has type `Unit` you know it must be `tt`. It contains no information, but you can easily build it
David Young
CC BY-SA 4.0
2023-06-06T03:19:35.997Z
Now that I think of it, though, using the unit type like that is only one approach. You could actually the incorporate ideas from the other answers into this and choose something else for `T`. In fact, you could implement each of those inside a language that has this sort of action type.
David Young
<p>The main issue is that a <code>for</code> loop might iterate 0 times. For a C-style <code>for(init; cond; step)</code> loop the condition might be initially false; for a for-each loop over an iterable, the iterable may be empty. So there needs to be some value which is returned in that case. (If you only allow <code>do</code>-<code>while</code> loops then you can avoid this problem, but the language will not be as expressive.)</p>
<p>Therefore the sensible way of returning a value seems to be as an <a href="https://en.wikipedia.org/wiki/Option_type" rel="nofollow noreferrer">option type</a>: if the loop iterates at least once, then the value is <code>Some(x)</code> where <code>x</code> is the value of the body on the last iteration, otherwise if the loop iterates zero times then the value is <code>None</code>.</p>
<p>Alternatively, you could also allow Rust-style <code>break &lt;expr&gt;</code> as a way of returning a value from a loop expression, in which case the value could be some built-in sum type designed for the purpose (example below in Rust syntax):</p>
<pre class="lang-rust prettyprint-override"><code>enum ForResult&lt;S, T&gt; {
    None,
    Some(S),
    Break(T),
}
</code></pre>
<p>However, this may cause some inconveniences. If <code>ForResult</code> is used for every <code>for</code> loop even if there is no <code>break</code> expression inside it, then the type system will force the programmer to handle the impossible <code>Break(_)</code> case. On the other hand, if a regular <code>Option</code> is used for loops with no <code>break</code> expressions, then two <code>for</code> loops could have incompatible types when the programmer wants them to be compatible (e.g. in the two branches of an <code>if</code>/<code>else</code> expression).</p>
<p>Lastly, it's worth mentioning that in Rust a <code>for</code> loop <em>does</em> have a value, namely <code>()</code>, a unit value. This is a perfectly viable option for an expression-oriented language, though probably not what you were looking for as an answer.</p>

CC BY-SA 4.0
2023-06-03T00:25:51.633Z
2023-06-03T13:27:01.810Z
2023-06-03T13:27:01.810Z
kaya3
CC BY-SA 4.0
2023-06-03T23:54:22.057Z
"The main issue is that a for loop might iterate 0 times" The common solution to that is to require an `else` branch.
Longinus
CC BY-SA 4.0
2023-06-04T11:54:18.560Z
@Longinus For a loop? I know that Python has `else` branches for `for` loops, but they aren't required, and they don't only execute when the loop iterates zero times. (They execute whenever the loop completes without `break`ing.) I see you've mentioned Zig as an example in your answer ─ are there any others which do require loops to have `else` clauses?
kaya3
CC BY-SA 4.0
2023-06-04T16:22:07.853Z
I think so, unless my memory merged the two orthogonal concepts. I'll have to go through the FLL and AltJS corpora (of which I have been procrastinating for 6~8 months instead of porting them to PLDB) to find them. I'll report back later.
Longinus
# What would a for loop return in an expression oriented language?
Posted by: username_0 on 2023-06-03
An expression-oriented language sounds appealing, as I could have code logic inline in expressions and initializers. An if statement could just return the last expressions in their blocks similar to in Rust I believe:
x = if condition {y} else {z}

A switch could similarly return the last expression in their case blocks.
However, what could a loop return in a way that would be useful?
**Tags:** expressions loops for-loops
> **Comments:**
> **username_1**: Mathematica is expression-oriented but dynamically typed. Loops in Mathematica simply returns `None`, unless you `Break` with some value.  
---
## 5 answers
### Answer by username_4 on 2023-06-03. Score: 10 | Accepted answer
I like the answer from kaya3, but I think there's a better option.
Treat the body of the for loop as an expression itself, and treat every iteration as evaluating that expression. Then put the results in a list and return that list as the result of the loop.
In other words, this would be perfectly valid:
squares: []int =
for (i = 1; i <= 10; i += 1)
{
    i * i;
};

(I apologize if this isn't readable. This code is using the syntax of my own language.)
After this code, squares would be a list of the squares up to 100.
This has a few advantages.

After the loop, you know how many times the loop executed because that's the length of the list.
The only special case you need to worry about is an empty list, but that's a typical case for lists anyway.
Finally, you wouldn't have to differentiate between iterations that ended normally, used continue, or used break; they all can return a value and have those values added to the list.

My language does this. In fact, it does this for every kind of loop, which makes things really convenient.
Say you have a list of users, and you want a list of just their favorite things. You would write this:
faves: []Favorite =
foreach (user: users)
{
    user.favorite_thing;
}

And then I don't feel so bad!
> **Comments:**
> **username_5** (2023-06-03): That sounds like `.map` with extra steps.  
> **username_4** (2023-06-03): It is, and that's where the idea came from. My examples don't show this, but sometimes, it's more readable to have a complicated loop with a body of code instead of a complicated lambda. And `map` would make it complicated to do an early `break`, for example.  
> **username_5** (2023-06-03): Early `break` could be done by running until some sentinel value (like `nil`), or by passing a `ref bool` to the callback.  
> **username_4** (2023-06-03): And then running `filter`. What I meant by early `break` was to break out before all items were processed, something `map` does not do by itself. Also, doing that sort of thing in `map` then `filter` would require repeating the break condition. `map` is great, but sometimes a loop is better.  
> **username_5** (2023-06-03): I was thinking something more like [this](https://swiftfiddle.com/stk74cuemzfw5hdsm7q6q3kk6q).  
> **username_4** (2023-06-03): I did not know that Swift had that. Nevertheless, I am confident that some cases will be better written as a loop.  
> **username_6** (2023-06-03): That could become a devastating memory sinkhole very easily, though. For instance, an event loop, intended to run for as long as the program is, would consume more and more memory over time, eventually resulting in an out-of-memory crash that wouldn't be identifiable at compile time or with unit tests. I'd say this would be better off as an explicit opt-in feature  
> **username_4** (2023-06-03): @abel1502 in my language, if a loop body returns `void`, the compiler skips generating the coffee that would generate the list. Most uses of a loop as a statement, such as an event loop, fall under this and thus, do not become memory sinkholes.  
> **username_3** (2023-06-04): This is what list comprehensions are, e.g. `[i * i for i in range(1, 11)]` in Python. But most of the time you need a loop, you don't also need to build a list; and building a list isn't free. Perhaps a clever compiler can eliminate the list-building when it can prove the list isn't used, but that's a lot of extra work if you want to handle cases like just accessing the list's length, or just accessing its last element. So this is a useful feature for a language to have but I don't see how it's better to make all loops work like this.  
> **username_3** (2023-06-04): Doing this for every kind of loop also seems very limiting ─ server applications tend to have long-running loops, combinatorial search algorithms may have loops which iterate millions or billions of times, games have loops which iterate 60 times per second ─ even ignoring the time it takes to push things to a list, this is practically a memory leak. Sure, you can have a carve-out for loop bodies of type `void`, but this means that a missing semicolon can blow up a program if you leave it running long enough. I think the answer would be improved by discussing some of these limitations.  
> **username_3** (2023-06-04): Actually, going by your example syntax, even a semicolon seems not to be enough, otherwise `i * i;` would be of type `void` rather than `int`. So a loop like `for(...) { foobar(); }` will build a list if `foobar()` isn't of type `void`, even if the user only called it for its side-effects.  
> **username_4** (2023-06-04): My language uses semicolons everywhere, unlike Rust. Anyway, it also errors on unused results. If you have a non-void loop that you don't assign to anything, it will error. So every loop that is not void will be easily seen.  
> **username_4** (2023-06-04): Also, list comprehensions can easily become non-readable. And having this means I don't need special syntax for that.  
> **username_4** (2023-06-04): And before you ask, yes, I know that an unused result as the last expression in a block will not be "unused" since it will make the block return that value. However, that result will propagate up to the top-level scope, where it will be unused. As long as the compiler can trace the entire propagation, the error message will be useful, and the user can figure out where they missed something. Other than that, unused results are trivially detectable at the spot.  
---
### Answer by username_8 on 2023-06-06. Score: 7
The setup
I'll take a different approach than the other answers.
What if we actually allow statements to be a new kind of expression, different from the others? What if that new kind of expression even had its own operations? Let's find out!
Let's say that in our language we actually "embed" the statements into the expressions like this.
A statement can be thought of as an "action." Two actions can be combined to form another action. For example, if you have an action x and an action y, you can use a sequencing operator (call it ;;) to combine them into a new action. This new action will first do the x action and then do the y action.
Say we want to print 1 and then print 2. These are two actions. You might write this print(1) ;; print(2). Of course, you can come up with much better syntax, this is just for illustration. ;; is a binary operation that takes two actions and gives you back another action (the "combination" of the two argument actions).
While loops
Alright, now what is a loop in this language? Well, let's start with a while loop. We can actually think of a while loop as a function that takes a Boolean expression (the conditional) and an action (the body) and returns an action. The action it returns is the action that repeats the while loop body action while the conditional is true.
while actually doesn't need to be a keyword here and, in fact, there are ways you could implement it inside standard library of the language.
You could write it like this (again using a made-up syntax purely for illustration; there are much better ones):
while(x > 5,
  (print(x) ;; x := x-1))

It looks like I've just written a while loop in a super weird way. And that is technically true. However, we've also gained something new. Actions are now first-class values in our language!
We can actually pass around the loop body I've written above. Say you want to abstract the notion of "looping 15 times." You can write a function
repeat15 =
  fun(a) =>
    let i := 0
    in
    while(i < 15, (a ;; i := i+1))

Now you could write
repeat15(print("*"))

and you'll print out 15 *s.
Also, if we wanted to, we could distinguish actions from "regular values" (like integers or strings) at a type level. We can do this by creating an Action type for actions that produce a value of type T. We can distinguish between a string value we already have in our hand from an action that produces a string value. And, furthermore, we can pass both of those kinds of things around at will.
But what is the difference between an Action and an Int? Well, it's the exact same as the difference between /bin/ls and a list of files (paraphrasing a quote from Shachaf).
For loops
A for loop (or a foreach loop) can be thought of as a function that takes in a list and a "parameterized action" (a function that gives you back an action). It returns an action that does this: it will take each item of the list in turn and give it to the "parametrized action."
For instance,
for([1,2,3,4], fun(x) => print(x))

would print 1 2 3 4 (writing spaces for newlines).
So, what does a for loop return here? It returns an action! It doesn't return any particular value produced by the loop. It returns an action that actually performs the loop itself.
I mentioned that we can also have a version of ;; that passes a result from the first action to the second. Here's what that could look like. Call that operator -->:
getLine() --> (fun(x) => print(length(x)))

This would read in a line from standard input and print its length.
Note that ;; is just a special case of this:
a ;; b

is the same as
a --> (fun(_) => b)

As I've mentioned, you can come up with a better syntax. In fact, there already are better syntaxes. The reason is that these ideas already exist.
And that leads me directly to...
The punchline
What I've just described is a particular case of the concept of a monad (in the form in which it appears in programming languages).
Now, there is some debate about how people should learn about monads. Ultimately, I can only provide part of the answer for this.
I've only described part of one specific monad here. I believe to learn them properly you must experiment with a variety of them. For example, look at the list monad, the reader monad and the proxy monad. There are plenty more, but that is some variety to start with. Think about how the "bind" operation works for each of them works, in each particular case.
In the context of this question, the list monad in particular would be useful to look at. Also, my answer to a question here talks about the Maybe and Either monads in a somewhat similar style to this answer.
I also did not describe the mathematical laws they must obey (though there is a relatively intuitive way to look at these laws in terms of actions).
I've only scratched the surface of a particular monad here, but hopefully this answer can provide some useful ideas.
The particular monad I've described is roughly like Haskell's IO monad, though I've left all calls to writeIORef, readIORef and newIORef implicit. Also, the let-in in the repeat15 should really be a call to -->. I've simplified that for the purposes of demonstration. There are many other monads, as well. It remains the case that --> is the basic "action combining" operation. In Haskell, this is called >>=. Likewise, the Haskell name for ;; is >>. There's also an operation that will take a "regular value" and give you an action that simply produces that value without doing anything else. This is sometimes called (in Haskell) pure or (confusingly) return. It's also sometimes called "unit."
> **Comments:**
> **username_9** (2023-06-06): Welcome to PLDI! This is a great answer, though I guess it does create a bit of a chicken-and-egg if I'm reading it right...what's the `T` in the `Action<T>` for a `for` loop? :p  
> **username_8** (2023-06-06): @RydwolfPrograms Good question! In that case, it would be the "unit type." This is a type that only has a *single* value. In Haskell this is called `()` and in Standard ML it is called `unit`. So, in our sort of made up syntax, let's call this type `Unit` and we could write `Action<Unit>`. Let's use the name `tt` for the only value of type `Unit`. Now, let's contrast this with `Bool`. If you have a variable `x` of type `Bool`, you know it must either be `true` or `false`. On the other hand, if `y` has type `Unit` you know it must be `tt`. It contains no information, but you can easily build it  
> **username_8** (2023-06-06): Now that I think of it, though, using the unit type like that is only one approach. You could actually the incorporate ideas from the other answers into this and choose something else for `T`. In fact, you could implement each of those inside a language that has this sort of action type.  
---
### Answer by username_3 on 2023-06-03. Score: 7
The main issue is that a for loop might iterate 0 times. For a C-style for(init; cond; step) loop the condition might be initially false; for a for-each loop over an iterable, the iterable may be empty. So there needs to be some value which is returned in that case. (If you only allow do-while loops then you can avoid this problem, but the language will not be as expressive.)
Therefore the sensible way of returning a value seems to be as an option type: if the loop iterates at least once, then the value is Some(x) where x is the value of the body on the last iteration, otherwise if the loop iterates zero times then the value is None.
Alternatively, you could also allow Rust-style break  as a way of returning a value from a loop expression, in which case the value could be some built-in sum type designed for the purpose (example below in Rust syntax):
enum ForResult {
    None,
    Some(S),
    Break(T),
}

However, this may cause some inconveniences. If ForResult is used for every for loop even if there is no break expression inside it, then the type system will force the programmer to handle the impossible Break(_) case. On the other hand, if a regular Option is used for loops with no break expressions, then two for loops could have incompatible types when the programmer wants them to be compatible (e.g. in the two branches of an if/else expression).
Lastly, it's worth mentioning that in Rust a for loop does have a value, namely (), a unit value. This is a perfectly viable option for an expression-oriented language, though probably not what you were looking for as an answer.
> **Comments:**
> **username_7** (2023-06-03): "The main issue is that a for loop might iterate 0 times" The common solution to that is to require an `else` branch.  
> **username_3** (2023-06-04): @Longinus For a loop? I know that Python has `else` branches for `for` loops, but they aren't required, and they don't only execute when the loop iterates zero times. (They execute whenever the loop completes without `break`ing.) I see you've mentioned Zig as an example in your answer ─ are there any others which do require loops to have `else` clauses?  
> **username_7** (2023-06-04): I think so, unless my memory merged the two orthogonal concepts. I'll have to go through the FLL and AltJS corpora (of which I have been procrastinating for 6~8 months instead of porting them to PLDB) to find them. I'll report back later.  
---
### Answer by username_7 on 2023-06-04. Score: 6
A common approach at stateful loops in languages with expression-oriented syntaxes is to see them as conditionals where the loop either stops itself with a result, or invokes an else branch.
Hence,
let x = while y do z else w

is interpreted as
let x =
    let _tmp = None
        rec _f() =
            if ((_tmp is None) and y) then (z; _f())
            else ()
        _ = _f()
        in
    if _tmp then _tmp
    else w

where _tmp holds the eventual result (in the below example set by break).
An actual, useful, example may look like this:
find[A](x: A, xs: Iterable(A)): Maybe(Index(xs)) =
    let i = 0 in
    foreach e in xs do
        if e == x then
            break i
        else
            i += 1
    else
        None

test "loop" with
    assert(find(5, 0.toIncluding(10)) == 5);
    assert(find(5, positiveEvenIntegrals.toIncluding(10)) == None)

Zig is a precedent there. Python and Ruby both have related features but lacking the returned value aspect.
---
### Answer by username_2 on 2023-06-03. Score: 3
One option is outputting the loop guard. This has the advantage of always having a value even if the loop is run 0 times, but somewhat restricts how the initialization of the loop could work.
For example, consider code like this:
let i = 0;
let d = for (b=1; i<10; i++) {
    b*=2;
}

Here, it d will equal 1024, the final value of b. Note we type b=1 not let b=1 since the variables declared in that place have special meaning and that position in the loop should not be confused with a normal statement.
It could even work with normal variables if your language supports tuples:
let x,y = for (b=1, i=0; i<10; i++) {
    b*=2;
}
// x=1024, y=10

The advantages of this method are:

Always produces a value even if the loop terminates after 0 iterators
Outputting the final value after repeated operation is potentially quite useful

Disadvantages:

Restricts types of operations that can be performed in the left most expression of a for loop
Requires declaring variables before the loop if you don't want them included in the output, polluting scope.
> **Comments:**
> **username_3** (2023-06-03): If the loop's value is always equal to the value of a specially nominated variable (`b` in this example), does it need to be the loop's value? You could equally write the same loop followed by `let d = b;`. I suppose there is the opportunity for this to be a shorthand when a loop appears in other places, but it's a non-obvious shorthand to me at least.  
> **username_2** (2023-06-04): @kaya3 True, I think this idea might be even more terrible than I thought  
> **username_3** (2023-06-04): It's not terrible! Just maybe it needs a syntax that makes clearer what value is produced.  
---
