About this series
It's sort of an open secret that I sometimes use ANSI M, better known as MUMPS. It was developed in the 60's, and it definitely still looks like something from the 60's. But it's 1,000 times uglier than anything from that decade. I've made plenty of people, from software testers at work to other developers on IRC, recoil in horror from showing them samples of even relatively mundane code like a simple "Hello, World!".
And now I want to inflict this abomination upon the rest of the world. But, since I have some conscience, I'm not going to tell you where to get an interpreter for this. You'll just have to find that out for yourself.
So, with that in mind, today's topic is line scope and block scope.
Line scope
M code is just a long series of commands sort of like unstructured BASIC. But, unlike BASIC, it supports multiple commands on the same line.
For example, these two code samples are equivalent:
new foo,bar,baz set foo=1 set bar=2 set baz=3
And:
new foo,bar,baz set foo=1 set bar=2 set baz=3
However, the three control flow commands if, else, and for do something weird, which I'll illustrate with these two contrived examples:
set spoon=$$getSpoon() if spoon>10 write "My spoon is too big",! write "Spoon is "_spoon,!
And:
set spoon=$$getSpoon() if spoon>10 write "My spoon is too big",! write "Spoon is "_spoon,!
What happens in the first example, assuming spoon=0, is it says "Spoon is 0", but not "My spoon is too big". The if's condition is false, so it skips the rest of the line (the first write command) and goes to the next line. The second example will go through both writes.
This makes sense though. Additional commands following an if on the same line are effectively inside the "then" block, so they should only run if the condiiton was true.
It's also possible to return from the function in this case:
isSpoonTooBig(spoon) ;
if spoon>10 write "My spoon is too big" quit 1
write "My spoon is too small"
quit 0
If spoon is too big, the function will say "My spoon is too big" and return 1 without doing anything else. Otherwise, it skips the write and quit on the first line and goes to the second line.
A note about for
But quit works differently in a for loop because for, unlike if and else, adds a new stack frame:
countToNum(num) ;
new count
for count=1:1:num write num,! quit:(count>100) write count
write "done",!
quit
This function looks like it's supposed to write each number it gets until it reaches num then write "done". But what happens when count>100? It doesn't quit the function; instead, it quits the for loop, so it still writes "done".
Block scope
The other syntactical oddity of M is block scope. Like in Python, blocks are delimited by some sort of header and indentation, and they are ended by a line that has less indentation. Unlike every other language in existence, M indents with .. So here's how you would do a multi-line if-then-else with line numbers:
01 if $$setUpTheBomb(somebody,us) do 02 . write "Somebody set up us the bomb",! 03 . if $$getSignal(we) do 04 . . do turnOn^MAINSCREEN 05 . . write "It's you !!",! 06 . . do main^CATS 07 . . for set zig=$order(^ZIG(zig)) do quit:'zig 08 . . . do takeOff(zig) 09 . . . do move(zig) 10 . . write "For great justice",! 11 . else write "No signal" 12 else write "No bomb"
So, assuming somebody set up us the bomb and we don't get signal, it would run from line 01 through the if in 03 then skip to 11.
But, if we did get signal, what exactly happens in the for loop on line 07? This:
- for: Start a new stack frame
- set zig=$order(^ZIG(zig)): gets the next key in ^ZIG after zig.
- do: This is the argumentless do. It will execute the following block, which is...
- do takeOff(zig). I bet you were expecting it to go to quit:'zig, but this is the argumentless do, which adds a stack frame and runs everything at the next higher level of indentation as an inline subroutine. So it calls do takeOff(zig) then...
- do move(zig). The next line is empty, so now it goes to...
- quit:'zig: If zig is 0 or the empty string, it quits the loop, going to step 7. Otherwise, it loops back to step 2.
- "For great justice"
I don't know why they designed it this way. This looks like it might be a cleaner way to do this:
01 for set zig=$order(^ZIG(zig)) do 02 . do takeOff(zig) 03 . do move(zig) 04 . quit:'zig
But it isn't. The quit in this revised version actually quits from the do, not from the for, so nothing's breaking the for loop, and you have become the proud parent of an infinite loop. Congratulations!