Infix while loops

By dkl9, written 2025-215, revised 2025-215 (0 revisions)


Sometimes I end up with code like this:

x = 0
while True:
    y = math.sin(x)
    print(x, y)
    if abs(y - 0.5) < 0.1:
        break
    x += 1
    input()

The code works, which is why I write and use it. But it always feels like an ugly hack: the true intent of the while-loop is hidden in the if abs(y) < 0.1: break. "While true ..." on its would mean "repeat forever", but this pattern betrays and dilutes that meaning.

If I want a more honest header for the loop, there are ways:

x = 0
y = math.sin(x)
print(x, y)
while abs(y - 0.5) > 0.1:
    x += 1
    input()
    y = math.sin(x)
    print(x, y)

But to keep the same behaviour, I had to duplicate code.

You could abstract the logic up to the loop condition into a function of its own.

def far(x):
    y = math.sin(x)
    print(x, y)
    return abs(y - 0.5) > 0.1

x = 0
while far(x):
    x += 1
    input()

But the loop body, after the condition, gets to stay expanded in-line. The asymmetry irks me.

Some languages offer a do-while loop, which would fix the issue in some cases like this. But if you want something to happen on each run of the loop except the last (here, input()), do-while doesn't simplify this. We can do better.

while and do-while are two bounding special cases of a more general pattern. With while, you put the condition and its implied goto at the start. With do-while, you put it at the end. With infix while, split the loop body as needed, and put the condition there.

x = 0
    y = math.sin(x)
    print(x, y)
while abs(y - 0.5) > 0.1:
    x += 1
    input()

This way, the loop goes as you want, sans repeated code or hidden loop conditions.

flowchart of an infix while loop

ยง Implementation

Ideally, this infix while syntax would be built into the programming language. But despite that Dahl already discovered it in 1972, well before me, it languishes unsupported.

As far as I've found, only Smalltalk fully supports infix while:

| x y |
x := 0.
[
    y := x sin.
    Transcript show: x printString, ' ', y printString; cr.
    (y - 0.5) abs >= 0.1
] whileTrue: [
    x := x + 1.
    Stdin nextLine
].

Some languages allow blocks of code as expressions, and hence as the condition for while. Thus their while loops are just as flexible, but keep a fully-prefix form:

x=0
while
    y=`echo "s($x)" | bc -ql`
    se=`echo "(${y}-0.5)^2*100" | bc -ql`
    echo "$x $y $se"
    [ "0${se%.*}" -ge 1 ]
do
    x=$((x+1))
    read
done

If a language lets you add your own syntax, like LisP with its macros, you can make infix while yourself:

(defmacro loop [#* body]
    (let [cond-index (body.index 'while-so) pre-cond (cut body (- cond-index 1))
        condition (get body (- cond-index 1)) post-cond (cut body (+ cond-index 1) None)]
        `(while True
            ~@pre-cond
            (when (not ~condition) (break))
            ~@post-cond)))

(setv x 0)
(loop
    (setv y (math.sin x))
    (print x y)
    (> (abs (- y 0.5)) 0.1) while-so
    (+= x 1)
    (input))

Some languages have a special syntax for "infinite" loops, like Rust's loop keyword. This makes it a tad clearer than while True that the loop breaks from somewhere within, for no loop is truly infinite.

Functional programming languages, like Haskell, sidestep this problem by not having while-loops at all.

main = loop 0
  where
    loop x = do
      let y = sin x
      putStrLn $ show x ++ " " ++ show y
      if abs (y - 0.5) < 0.1
        then return ()
        else do
          _ <- getLine
          loop (x + 1)

But functional programming is confusing.