The if statement is the primitive here, not while. In pseudocode, a while loop:
while cond do
can be “unravelled” (or “desurgared” if you will) to:
if not cond then goto L2;
In fact, if/goto is all that is needed to implement any type of structured flow control.
Python does not have goto
It does exist although it was introduced on April 1st.
I love the way Smalltalk did this. There is no intraprocedural flow control in the language at all. There are blocks (lambdas) with a call operator that corresponds to the number of arguments that they have, and message send (method invocation). Everything else is built from this.
Booleans in Smalltalk are represented by the true or false class. These implement if-true and if-false methods, which take a 0-argument block, which they call only for the matching method. Single-argument blocks themselves implement a while-true method, which invokes the block and then calls the if-true method on the result with a block that invokes the body and recurses. More complex flow control primitives can be built the same way.
For Verona, we’ve been exploring how this would work in a language with type inference and static dispatch. In theory, we could get away with two primitive flow control constructs in the language and use compiler inlining to make all library (standard-library or external-library) wrappers equal. There are some difficulties with the type signatures for these things in a language with strong ownership semantics (for example, you need to be able to express the type of a lambda that can be called only once, or possibly a set of lambdas where exactly one may be called to ensure lifetime safety). Smalltalk does no compile-time checking and uses dynamic checks for most of the error conditions.