Control Flow in MUMPS
Learn conditionals, loops, and branching in MUMPS, including its unique postconditionals, $SELECT, and the argumentless FOR/QUIT idiom that replaces the missing WHILE keyword
Control flow in MUMPS looks unusual to anyone coming from a C-family language. There are no curly braces, no switch, no while, and no for (init; condition; step) header. Instead, MUMPS gives you a small set of commands — IF, ELSE, FOR, DO, GOTO, QUIT — and a couple of distinctive features that do a surprising amount of work: postconditionals and the $SELECT function.
Two ideas drive almost everything. First, an IF doesn’t guard a block — it guards the rest of the line. When the expression is true, every command after it on that line runs; when it’s false, the line is abandoned and a system variable called $TEST is set to 0. Second, almost any command can carry a postconditional — a :condition suffix that lets the command run only when the condition is true. WRITE:x>0 "positive" writes nothing unless x is greater than zero.
Because MUMPS is typeless, a condition is just a number: zero is false and any non-zero value is true. Comparisons like age>17 produce 1 or 0, exactly the values the control-flow commands expect.
This tutorial covers conditionals, the four flavors of FOR loop, the WHILE-style idiom MUMPS uses in place of a while keyword, the $SELECT function that stands in for a ternary and a switch, and GOTO. As always, commands can be abbreviated to their first letter — I for IF, F for FOR, W for WRITE — but we’ll spell them out for clarity.
Conditionals: IF, ELSE, and Postconditionals
IF evaluates its argument and, if true, executes the remaining commands on the line. ELSE has no argument at all — it runs the rest of its line whenever $TEST (set by the most recent IF) is 0. Postconditionals attach a condition directly to a single command.
Create a file named controlflow.m:
controlflow ; Conditionals in MUMPS
; --- IF executes the rest of the line when the expression is true ---
set age=20
if age>17 write "Adult",!
if age<13 write "Child",!
;
; --- IF / ELSE rely on $TEST, set by the most recent IF ---
set score=45
if score>59 write "Pass",!
else write "Fail",!
;
; --- Postconditionals: a colon after the command name ---
; The command runs only when the condition is true.
set balance=-30
write:balance<0 "Account overdrawn",!
write:balance>0 "Account in credit",!
;
; --- Several commands can follow one IF on the same line ---
set hour=14
if hour<12 write "Good morning",! quit
if hour<18 write "Good afternoon",! quit
write "Good evening",!
quit
A few things worth noticing. ELSE is followed by two spaces — because it takes no argument, MUMPS needs the extra space to know where the (empty) argument ends and the next command begins. The same rule applies to the argumentless FOR and QUIT you’ll see below. On the last block, if hour<18 write "Good afternoon",! quit runs both the write and the quit when the hour is before 18, so execution never reaches the Good evening line.
Loops: The Four Faces of FOR
MUMPS has exactly one loop command, FOR, but it comes in several forms. The numeric form takes variable=start:increment:end. A negative increment counts down. The argumentless form loops forever until a QUIT stops it — which, combined with a postconditional, is how MUMPS expresses a while loop.
Create a file named loops.m:
loops ; Loops in MUMPS
; --- Numeric FOR: variable=start:increment:end ---
write "Up: "
for i=1:1:5 write i," "
write !
;
; --- A negative increment counts down ---
write "Down: "
for i=5:-1:1 write i," "
write !
;
; --- Step by 2 ---
write "Even: "
for i=2:2:10 write i," "
write !
;
; --- While-style loop: argumentless FOR with a QUIT postconditional ---
; MUMPS has no WHILE keyword; this is the idiom.
set n=1,total=0
for quit:n>5 set total=total+n,n=n+1
write "Sum 1..5: ",total,!
;
; --- Block form: argumentless DO runs the dot-indented lines ---
set count=0
for i=1:1:3 do
. set count=count+i
. write "i=",i," count=",count,!
quit
The while-style line is the most important idiom here: for quit:n>5 set total=total+n,n=n+1. The argumentless FOR (note the two spaces) repeats the rest of the line forever. On each pass it first runs quit:n>5 — a QUIT that fires only once n exceeds 5, breaking the loop — and otherwise falls through to the SET. To loop over more than a line’s worth of work, end the FOR with an argumentless DO and write the body as dot-indented lines (.), as the final block shows.
$SELECT and GOTO
MUMPS has no switch statement and no ?: ternary operator. The $SELECT function fills both roles: it scans a list of condition:value pairs left to right and returns the value of the first condition that is true. A trailing 1:default acts as the catch-all else. GOTO (abbreviated G) jumps to a label and, like every command, accepts a postconditional.
Create a file named dispatch.m:
dispatch ; $SELECT and GOTO in MUMPS
; --- $SELECT returns the value after the first true condition ---
for temp=50,72,95 do
. set label=$select(temp<60:"cold",temp<80:"mild",1:"hot")
. write temp,"F is ",label,!
;
; --- $SELECT as an inline ternary (if/else expression) ---
set n=7
write n," is ",$select(n#2=0:"even",1:"odd"),!
;
; --- GOTO transfers control to a label (use sparingly) ---
set tries=0
retry ; loop target
set tries=tries+1
write "Attempt ",tries,!
goto:tries<3 retry
write "Done after ",tries," attempts",!
quit
$SELECT requires at least one condition to be true — if none match, it raises an error — which is why the idiomatic catch-all 1: (always true) appears last. The FOR temp=50,72,95 form loops over an explicit comma-separated list of values rather than a numeric range, a handy variant when the values aren’t evenly spaced. The GOTO block builds a counted loop by hand: the postconditional goto:tries<3 retry jumps back to the retry label until tries reaches 3.
Putting It Together: FizzBuzz
FizzBuzz combines a counted FOR, the modulo operator (#), IF with postconditional-style early QUIT, and a subroutine call. DO label(arg) calls a subroutine, passing arg to the formal parameter named in the label.
Create a file named fizzbuzz.m:
fizzbuzz ; FizzBuzz - combining MUMPS control flow
for i=1:1:15 do check(i)
quit
;
check(n) ; Classify one number, n
if n#15=0 write "FizzBuzz",! quit
if n#3=0 write "Fizz",! quit
if n#5=0 write "Buzz",! quit
write n,!
quit
Each IF line ends with a QUIT that returns from the check subroutine the moment a match is found, so only one label is ever printed per number. Remember that MUMPS has no operator precedence: n#15=0 evaluates strictly left to right as (n#15)=0, which is exactly what we want.
Running with Docker
| |
Expected Output
Running controlflow:
Adult
Fail
Account overdrawn
Good afternoon
Running loops:
Up: 1 2 3 4 5
Down: 5 4 3 2 1
Even: 2 4 6 8 10
Sum 1..5: 15
i=1 count=1
i=2 count=3
i=3 count=6
Running dispatch:
50F is cold
72F is mild
95F is hot
7 is odd
Attempt 1
Attempt 2
Attempt 3
Done after 3 attempts
Running fizzbuzz:
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
Key Concepts
IFguards the rest of the line, not a block. When the expression is true, every command after it on the same line runs; when false, the line is abandoned and$TESTis set to 0.ELSEtakes no argument and runs its line whenever$TESTis 0. Argumentless commands (ELSE, argumentlessFOR, argumentlessQUIT) must be followed by two spaces.- Postconditionals are everywhere. Almost any command accepts a
:conditionsuffix —write:x>0,quit:done,goto:more retry— so it runs only when the condition is true. - There is one loop command,
FOR, with four forms: numeric range (i=1:1:5), stepped or descending range (i=5:-1:1), explicit value list (temp=50,72,95), and argumentless (for ...) for open-ended looping. - MUMPS has no
WHILE. The idiomfor quit:condition body— argumentlessFORplus aQUITpostconditional — is the standard substitute. $SELECTreplaces bothswitchand the ternary operator. It returns the value after the first truecondition:valuepair; end with1:defaultso it never falls through with no match.- Conditions are just numbers. Zero is false, any non-zero value is true, and comparisons return
1or0— there is no distinct boolean type. - Blocks use dot indentation. End a
FOR(orIF, orDO) with an argumentlessDOand write the body as.-prefixed lines to group multiple statements under one controlling command.
Running Today
All examples can be run using Docker:
docker pull yottadb/yottadb-base:latest-master
Comments
Loading comments...
Leave a Comment