In part one of this series I presented a summary of how the DivConq database connector for M (MUMPS) will work. Then we covered how to install M (GT.M software) and how to get to the M prompt.
In part two of this series I presented a review of the basics of the M programming language.
In this third part we’ll examine how data is persisted in M why the M programming language is well suited to data access.
To get started you’ll want two SSH sessions. Login as an admin user (e.g. ec2-user). One of those sessions will be to the M command prompt so get to the gtmuser’s M prompt:
$ sudo su gtmuser $ cd ~ $ ./gtmcon
The other SSH session will be used for editing M routines. So get to the gtmuser’s shell prompt in the routines directory:
$ sudo su gtmuser $ cd ~/.fis-gtm/r/
Lets start with the M prompt. Recall that M does REPL from the prompt. So lets start with attempting to create a record of data – we want to keep track of people:
GTM>s Name="Sally" GTM>s Age=8 GTM>zwr Age=8 Name="Sally" GTM>
There are two problems with this. First it does not persist, halt out of M and return, “zwr” and nothing. Second, what if we have more than one person? Do we do Name1, Name2, Name3? No, that design would never grow with the code.
Lets fix the first problem. In M there is only one kind of data structure. This structure holds maps (key-value pairs) and the maps can be nested (in a matter of speaking). To provide a key to a map include the key in parenthesis after the variable name. Similar to Javascript’s object and Python’s dictionary, both of which use square brackets instead of parenthesis.
GTM>s Person("Name")="Sally"
GTM>s Person("Age")=8
GTM>zwr Person
Person("Age")=8
Person("Name")="Sally"
GTM>
We don’t have our persistence yet. However we have achieved something useful, our data is all in one variable now. How do we persist it? Just put a ^ in front of the variable name:
GTM>s ^Person("Name")="Sally"
GTM>s ^Person("Age")=8
GTM>zwr ^Person
^Person("Age")=8
^Person("Name")="Sally"
GTM>
Now halt out of M and return.
GTM>h
$ ./gtmcon
GTM>zwr ^Person
^Person("Age")=8
^Person("Name")="Sally"
GTM>
The data should be there. That’s all there is to persistence, but a ^ in front of any variable name and it becomes a “m global”. Note that Person and ^Person are two different things, you can not set values to one and get them from the other.
While M does have globals (with ^) and locals (without ^), as already mentioned, there is only one data structure. To prove that set a value right into ^Person:
GTM>s ^Person=20015
GTM>zwr ^Person
^Person=20015
^Person("Age")=8
^Person("Name")="Sally"
GTM>
We’ll come back to that in a minute, next lets solve the second problem – how to keep track of more than one Person. What we may want is some sort of list of people, but M does not have arrays in the traditional sense. Still, M does allow numeric keys and as such we can produce a list by using keys 0, 1, 2, 3, etc…
Here is a list of names:
GTM>s ^Names(0)="Sally" GTM>s ^Names(1)="Chad" GTM>zwr ^Names ^Names(0)="Sally" ^Names(1)="Chad" GTM>
Now, where M starts to get especially interesting is the ability to nest keys. Keys are all listed in the parenthesis and are separated by commas.
GTM>s ^People(0,"Name")="Sally" GTM>s ^People(0,"Age")=8 GTM>s ^People(1,"Name")="Chad" GTM>s ^People(1,"Age")=9 GTM>zwr ^People ^People(0,"Age")=8 ^People(0,"Name")="Sally" ^People(1,"Age")=9 ^People(1,"Name")="Chad" GTM>
Internally M has two top level keys for People (0 and 1) and then each of those keys has two sub-keys (“Name” and “Age”).
Naturally you can use a variable instead of a literals for a key. So if “i=1″ we can print “Chad” like this:
GTM>s i=1 GTM>w ^People(i,"Name"),! Chad GTM>
Now lets make use of “for” from Part 2. Or goal is to loop through all the people in the global and print their name. To do so we will use the “$o” function. M comes with a few built-in functions and they all start with a single “$”. “$o” allows us to loop through a key level (aka a subscript). Consider:
GTM>w $o(^People(0)) 1 GTM>
Notice that we did not include the “Name” or “Age” in this use of ^People. We are telling $o which key level to loop on by ending the parenthesis after the first key – the last key listed is the one we are looping on. Note that the result (1) is the key after 0 on that key level. And that is what $o does, it returns the key following the key given in the argument. (“following” means in canonical order – numbers first then alpha from “A” to “z” and on with unicode)
Further, the key does not have to exist. “-1″ is not a key in ^People, yet:
GTM>w $o(^People(-1)) 0 GTM>
So to loop an “array” in M we can start at -1. However, M programmers typically use “” (M’s null) because null precedes any other value – AND because $o returns “” if no key follows. So consider “” to be $o friendly.
That for loop (goal listed above) to loop through all the people in the global and print their name is accomplished with:
GTM>s i="" f s i=$o(^People(i)) q:i="" w ^People(i,"Name"),! Sally Chad GTM>
Note that “i” starts with “”, so when we come to $o the first time we then get a return of “0″. Next we check to see if “i” is null and break from the loop if it is. If not we write the name of that person and return to the “for” command. At the “set” command “i” is “0″, after $o is called “i” will be “1″. Print the second name. Loop again, this time $o returns “” because there are no more keys. The “quit” condition is met and we break from the for loop.
Lets go back to the other command prompt and edit our routine “intro1″ again. To fully demonstrate the power of $o we need to see $o operating on more than one key level. Add the following function to intro1.
; dumpPeople ; s i="" f s i=$o(^People(i)) q:i="" d . w "Person: "_i,! . s fld="" f s fld=$o(^People(i,fld)) q:fld="" d . . w " "_fld_": "_^People(i,fld),! quit ;
Back in M link and run
GTM>zl "intro1" GTM>d dumpPeople^intro1 Person: 0 Age: 8 Name: Sally Person: 1 Age: 9 Name: Chad GTM>
So you can see that M can loop on multiple key levels. This can be true even with a complex index global such as:
s a="" f s a=$o(^Index(a)) q:a="" d . s b="" f s b=$o(^Index(a,b)) q:b="" d . . s c="" f s c=$o(^Index(a,b,c)) q:c="" d . . . s d="" f s d=$o(^Index(a,b,c,d)) q:d="" d . . . . s e="" f s e=$o(^Index(a,b,c,d,e)) q:e="" d
Two more things to know about $o. One, it works with locals too:
s a="" f s a=$o(Index(a)) q:a="" d . s b="" f s b=$o(Index(a,b)) q:b="" d
and two, you can loop in reverse canonical order by using a -1 as the second argument to $o:
s a="" f s a=$o(Index(a),-1) q:a="" d . s b="" f s b=$o(Index(a,b),-1) q:b="" d
Update your function in intro1 to reverse the order of people listed:
; dumpPeople ; s i="" f s i=$o(^People(i),-1) q:i="" d . w "Person: "_i,! . s fld="" f s fld=$o(^People(i,fld)) q:fld="" d . . w " "_fld_": "_^People(i,fld),! quit ;
Back in M link and run
GTM>zl "intro1" GTM>d dumpPeople^intro1 Person: 1 Age: 9 Name: Chad Person: 0 Age: 8 Name: Sally GTM>
So finally lets create a name index for our people. We need some people with overlapping names:
GTM>s ^People(2,"Name")="Billy" GTM>s ^People(2,"Age")=7 GTM>s ^People(3,"Name")="Sally" GTM>s ^People(3,"Age")=5 GTM>s ^People(4,"Name")="Billy" GTM>s ^People(4,"Age")=10 GTM>zwr ^People ^People(0,"Age")=8 ^People(0,"Name")="Sally" ^People(1,"Age")=9 ^People(1,"Name")="Chad" ^People(2,"Age")=7 ^People(2,"Name")="Billy" ^People(3,"Age")=5 ^People(3,"Name")="Sally" ^People(4,"Age")=10 ^People(4,"Name")="Billy" GTM>
And then lets write some code to make the index. Add another function to intro1 like this:
; indexPeople ; s i="" f s i=$o(^People(i),-1) q:i="" d . s name=^People(i,"Name") . s ^Index(name)=^Index(name)+1 . s ^Index(name,i)=1 quit ;
Save and run it:
GTM>zl "intro1" GTM>d indexPeople^intro1 GTM>
Lets see what that did:
GTM>zwr ^Index
^Index("Billy")=2
^Index("Billy",2)=1
^Index("Billy",4)=1
^Index("Chad")=1
^Index("Chad",1)=1
^Index("Sally")=2
^Index("Sally",0)=1
^Index("Sally",3)=1
GTM>
At a glance we can see that after the first key is a value (e.g. 2 in ^Index(“Sally”)=2) that tells use how many people have this name. In a second key level is the position of the person in the list. So we know person 0 and 3 are both “Sally”. If we want the ages of all the Sallys?
GTM>s i="" f s i=$o(^Index("Sally",i)) q:i="" w ^People(i,"Age"),!
8
5
GTM>
What if we want to find all the names that start with “C”? Assuming there is no name that is just “C”:
GTM>s i="C" f s i=$o(^Index(i)) q:(i="")!($e(i)]"C") w i,! Chad GTM>
Note that $e is another built-in M function, it extracts a character from the argument. In this case the first character. So when “i” became “Sally” then $e returned “S”. “S” does follow “C” (as would “D”) so we break from the loop instead of printing “Sally”. Of course we skipped “Billy” because we started with “C” and $o returns the following key (“Chad” in this case).
But what if a name did start with “C”? How would we solve that, because the above code looks for after “C”, exclusive. Here is a solution.
GTM>s i=$o(^Index("C"),-1) f s i=$o(^Index(i)) q:(i="")!($e(i)]"C") w i,!
Chad
GTM>
We initialize “i” with the key immediately preceding “C”. Thus our next call will give us “C” or beyond. This solution is not 100% thread safe, but then none of the code we have covered yet is.
That’s it for this part of the series, the 4th and final will cover some finer points of M programming that may not be obvious. That includes thread safety!
There are a few other articles on the web about M globals. One of the more active members of the M community is Rob Tweed – check out his description of M globals:
Rob Tweed’s paper on M Globals – Choose “Misc” and then “Mumps Globals Explained”
Also, if you have not done so already, you may wish to read the MUMPS entry on Wikipedia:
Published on Friday December 09, 2011 at 05:49pm