32

MUMPS Masochism part II: Indirect and Naked References

 5 years ago
source link: https://www.tuicool.com/articles/hit/2iiE7bA
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Last time, I talked about the insanity of M's block and line scope . This time, I have something hopefully less insane: indirect and naked references

Background

M's main selling point is its array structure. Its variables are essentially either multidimensional arrays with an arbitrary number of dimensions or trees with an arbitrary number of levels. So we could have code like this:

set ^DATA("players",1,"name")="John Egbert"
set ^DATA("players",1,"alias")="ghostlyTrickster"
set ^DATA("players",1,"class")="Heir"
set ^DATA("players",1,"aspect")="Breath"
set ^DATA("players",1,"color")="blue"

But since M is such an ancient language, it was used in times when system memory and storage space were measured in kilobytes or, for those lucky enough to have access to mainframes, megabytes. So code had to be as short as possible while still doing everything it needed to do. One trick M has for this is that most commands have a short form, so instead of writing do , kill , quit , we can do d , k , q .

But what can we do to make that code above less huge?

Indirect references

One option is to use indirect references (shown here with short commands):

n ref ; short for "new ref". Declares "ref" as a local variable.
s ref=$name(^DATA("players",1))
s @ref@("name")="John Egbert"
s @ref@("alias")="ghostlyTrickster"
s @ref@("class")="Heir"
s @ref@("aspect")="Breath"
s @ref@("color")="dark blue"

Here, ref is being used as an indirect reference. @ref is equivalent to ^DATA("players",1) . The intrinsic function $name returns the name of its argument as a string. We could also write the 2nd line as s ref="^DATA(""players"",1)" but that's ugly since it needs to escape the quotes.

But there's a potential danger to this. Consider the following routine:

main ;
 new pointer
 set ^TEST(1,2,3)="set from main"
 set pointer="^TEST(1,2,3)"
 ;
 write "before calling indirectRef with a global:",!
 write ?2,"^TEST(1,2,3) "_^TEST(1,2,3),! ; should print "set from main". "?2" indents this at least 2 spaces.
 write ?2,"pointer: "_pointer,!          ; should print "^TEST(1,2,3)"
 write ?2,"@pointer: "_@pointer,!        ; should print "set from main"
 write !
 ;
 do indirectRef(pointer)
 ;
 write "Values in main after calling indirectRef with a global:",!
 write ?2,"^TEST(1,2,3): "_^TEST(1,2,3),! ; should print "set from indirectRef"
 write ?2,"pointer: "_pointer,!           ; should still be "^TEST(1,2,3)"
 write ?2,"@pointer: "_@pointer,!         ; should print "set from indirectRef"
 write !
 ;
 new var
 set var="set from main"
 set pointer="var"
 write "before calling indirectRef with a local variable:",!
 write ?2,"var: "_var,!           ; should print "set from main"
 write ?2,"pointer: "_pointer,!   ; should print "var"
 write ?2,"@pointer: "_@pointer,! ; should print "set from main"
 write !
 ;
 do indirectRef(pointer)
 ;
 write "after calling indirectRef with a local variable:",!
 write ?2,"var: "_var,!           ; should print "set from main". But will it?
 write ?2,"pointer: "_pointer,!   ; should print "var"
 write ?2,"@pointer: "_@pointer,! ; should be the same as two lines before
 write !
 quit
;
indirectRef(var) ;
 set @var="set from indirectRef"
 quit

When we call main , we would expect the first call to indirectRef to set ^TEST(1,2,3) to "set from indirectRef" because pointer is set to "^TEST(1,2,3)" . So it would make sense for the second call to indirectRef to set var in the stack for main to "set from indirectRef" too, right?

Actually, no: it's still "set from main" . Inside indirectRef in the second call, var equals "var" . So @var points to var . But indirectVar has its own var in its stack, so we set it instead of setting var in main's stack.

To avoid this, remember the golden rule of indirect references: never pass an indirect reference referencing a local variable to a subroutine that also has a local variable with the same name .

Naked references

Since writing to the same subscripts in the same global several times in a row except with a different last subscript is such a common pattern in M, M has naked references as a shortcut. So we can do this:

s ^DATA("players",1,"name")="John Egbert"
s ^("alias")="ghostlyTrickster
s ^("class")="Heir"
s ^("aspect")="Breath"
s ^("color")="dark blue"

This, like indirect references, can be dangerous, like in this code:

main ;
 set ^TEST(1,"foo")="1,foo"
 ; This next line references ^TEST(1,"bar").
 set ^("bar")="1,bar"
 ; This should write "1,bar".
 write ^TEST(1,"bar"),!
 ; Let's set another explicit reference
 set ^TEST(2,"a")="2,a"
 ; Call a function from another routine.
 do doStuff
 ; Now do another naked ref.
 set ^("b")="2,b"
 ; What will this write?
 write ^TEST(2,"b"),!
 quit
 ;
doStuff ;
 set ^STUFF("status")="done"
 quit

You would think that last set would set ^TEST(2,"b")="2,b" . But doStuff just referenced ^STUFF("status") . So it actually sets ^STUFF(2,"b")="2,b" , and the last write either throws an error or prints a blank line (depending on error settings) because ^TEST(2,"b") doesn't exist.

This is what the globals look like after running this:

^TEST(1,"bar")="1,bar"
^TEST(1,"foo")="1,foo"
^TEST(2,"a")="2,a"

^STUFF("b")="2,b"
^STUFF("status")="done"

If doStuff were in another routine, you might not even see that it was referencing another global. Fortunately, nobody ever uses these anymore...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK