22
$\begingroup$

I've noticed that many template engines have a mechanism for detecting that a loop is not occurring.

Although their names and syntax vary, they all have this similar structure.

For example:

  • Jinja
{% for user in users %}
    {{ user }}
{% else %}
    This is only evaluated if `users` is empty
{% endfor %}
  • Jade
- var users = [];
ul
  each user in users 
    li= val
  else
    li This is only evaluated if `users` is empty
  • Hugo
{{ range $users }}
    {{ . }}
{{ else }}
    This is only evaluated if $users is empty
{{ end }}

But I have hardly seen similar constructs in general-purpose programming languages.

Notice: Python does not have this structure, these languages implement no-run detection, while python implements no-break detection.

What causes the disagreement between template language authors and general-purpose language authors?

$\endgroup$
1
  • 25
    $\begingroup$ To process zero items, there is nothing to do. But to display zero items, you often need to display something friendly like "No item found". $\endgroup$ Commented Nov 10, 2023 at 7:15

4 Answers 4

44
$\begingroup$

Template languages tend to format a document to show to a human, when doing a foreach on a set you are likely outputting a list of results.

It is good UX to have a explicit message when there are no results to show rather than to show nothing (which can look like a bug).

So there is high demand for a default handling of the zero count loop,

Whereas in imperative languages doing a loop means accumulating a result. An empty set will result in the initial value of the accumulator. You need that initial value anyway to initialize it and that starting value is often good enough as the result.

$\endgroup$
0
5
$\begingroup$

For exactly the same reason that most imperative programming languages that do iteration with for loops accept an empty collection to iterate over instead of throwing an error. It’s a very common case to need to account for, and it also is much easier to read if you don’t need the check for an empty collection.

Similarly, the general construct you show in your examples of doing something different if a collection of items is empty is a very common need when doing presentational templating (which accounts for a vast majority of template language usage), and it’s much easier to read a for/else syntax than it is to read an if/for/else construct.

Using Jinja as a point of comparison, here’s what that example would look like without for/else syntax:

{% if users|length > 0 %}
    {% for user in users %}
        {{ user }}
    {% endfor %}
{% else %}
    This is only evaluated if `users` is empty
{% endif %}

It’s not horrible, but there’s a lot more template language in there than in the for/else case, which is less than ideal especially given that this type of thing is likely to show up very frequently if you’re rendering things that users will be seeing.

$\endgroup$
2
$\begingroup$

I believe this is because general purpose PLs have been conservative, especially around syntax and reserved words, and they had no chance to absorb these highly useful ideas from template languages, which are newcomers to this landscape and prioritize convenience over tradition.

C had three-section for loop which influenced a lot of languages and which allowed for fine tuning of looping conditions, which almost nobody used.

Java used "smart for-loop" which Steve Yegge called "dumb for-loop" because you could do even less things with it than with traditional for-loop. Unfortunately, it did not catch further, and now you have better chances of seeing functional programming / Java streams / LINQ rather than any innovative structured programming constructs.

If you would be designing a programming language from scratch, of course you will have a way to iterate a collection, and it will have the ability to

  • Determine whether current item is first/last item being iterated.
  • Remove current item from collection being iterated.
  • Run an auxiliary code block when the collection was empty (that's what this question is about).

I'm placing some hopes on Kotlin which has rich syntax already.

$\endgroup$
7
  • $\begingroup$ Sorry, what is a PLA? $\endgroup$
    – kaya3
    Commented Nov 10, 2023 at 17:29
  • $\begingroup$ I'd say C's "for" loop is designed more to save vertical space than to allow "fine tuning". A loop design intended to facilitate fine tuning would specify that omitting the second clause would cause the first pass to be executed unconditionally, and repeat until evaluation of the third clause yielded zero. $\endgroup$
    – supercat
    Commented Nov 10, 2023 at 18:22
  • $\begingroup$ There are two common "for"-loop idioms in C (indexed loop, such as over an array, and until-null loop, such as over a list). The three-section loop syntax permits expressing both idioms with the same keyword (for(i = 0; i < 10; i++) and for(p = head; p != NULL; p = p->next)). $\endgroup$
    – Mark
    Commented Nov 10, 2023 at 23:24
  • $\begingroup$ I like this idea. I want to add a loop context keyword to detect the beginning and end of the loop and the number of loops. With static analysis, only the required flags will be recorded, this is zero overhead. but the problem is that multiple loops will have conflict. $\endgroup$
    – Aster
    Commented Nov 11, 2023 at 4:56
  • 1
    $\begingroup$ @Aster See how Kotlin does that, the syntax would be loop@Label ...; $\endgroup$
    – alamar
    Commented Nov 11, 2023 at 6:41
-3
$\begingroup$

Poor composition

Template languages tend to be less complex, and by so, care less for composition problems.

But a else clause in loops compose baldy with ifs:

if cond
each user in users 
  { ... }
else
  { ... } // of each, possibly empty, to guarantee that
else
  { ... } // this else, of if, is reachable.

On languages with named end delimitador this is a lesser problem (the Jinja example).

But in languages with with fixed or implied block end delimitation, this may cause a variation of dangling else problem.

$\endgroup$
2
  • 9
    $\begingroup$ This answer seems more like a critique of the design principle in general rather than an explanation why so many languages choose to use it regardless. "Because the designers were stupid" would not be a satisfying answer. You should at least try to explain what thought patterns lead to this design. $\endgroup$
    – Philipp
    Commented Nov 10, 2023 at 14:28
  • 7
    $\begingroup$ This problem isn't specific to using "else" for an empty collection ─ it also occurs with nested if statements, e.g. if(A) if(B) C else D ─ and in any case it goes away if this ambiguity in the grammar is fixed (e.g. by requiring braces). It also applies to for/else with other semantics, such as in Python where the else clause executes when the loop completes without breaking. So this answer isn't very responsive to the question, because it focuses on just the syntax of the feature. $\endgroup$
    – kaya3
    Commented Nov 10, 2023 at 17:32

You must log in to answer this question.

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