Get variables list

Hello!

In Caché there is a way to print all current variables using write command without arguments:

USER>set Einstein = 1879
USER>set Mozart = 1756
USER>write
Einstein=1879
Mozart=1756

But is there a way to get a list of this variables? I am looking for something that would return value like $LB("Einstein", "Mozart") for this case.

Thanks!

  • + 2
  • 0
  • 827
  • 12
  • 4

Answers

Something like this should do it:

set x=""

set x = $o(@x)

while x'="" {

 write !,x," ",@x

 set x = $o(@x)

}

This is awesome. It works! Wondering how many things COS has.

But it also outputs variable x. I wrapped your code into the method, and that's what I got:

ClassMethod GetVariables() As %List
{
  set UndefinedSpecialList = $LB()
  set UndefinedSpecialInt = 0
  set Undefined = ""
  set Undefined = $ORDER(@Undefined)
  while (Undefined '= "") {
    quit:($ORDER(@Undefined) = "UndefinedSpecialList")
    set $LIST(UndefinedSpecialList, $INCREMENT(UndefinedSpecialInt)) = Undefined
    set Undefined = $ORDER(@Undefined)
  }
  return UndefinedSpecialList
}

It works awesome until user specify variables named UndefinedSpecialList, UndefinedSpecialInt and Undefined. I suppose user won't ever name variables this way, of course, but the method which will consider this is the winner.

You could always use process private globals instead of local variable names if you want to avoid collisions in local variable names.

ClassMethod GetVariables() As %List
{
    Set ^||VariableNameList = ""
    Set ^||VariableName = ""
    Set ^||VariableName = $Order(@^||VariableName)
    While (^||VariableName '= "") {
        Set ^||VariableNameList = ^||VariableNameList_$ListBuild(^||VariableName)
        Set ^||VariableName = $Order(@^||VariableName)
    }
    Quit ^||VariableNameList
}

This might do some things you don't expect depending on variable scope, though - possibly relevant depending on the use case you have in mind.

Class Demo.Variables
{

ClassMethod OuterMethod()
{
    Set x = 5
    Do ..InnerMethod()
}

ClassMethod InnerMethod() [ PublicList = y ]
{
    Set y = 10
    Write $ListToString(..GetVariables())
}

ClassMethod NoPublicListMethod()
{
    Set y = 10
    Write $ListToString(..GetVariables())
}

ClassMethod GetVariables() As %List
{
    Set ^||VariableNameList = ""
    Set ^||VariableName = ""
    Set ^||VariableName = $Order(@^||VariableName)
    While (^||VariableName '= "") {
        Set ^||VariableNameList = ^||VariableNameList_$ListBuild(^||VariableName)
        Set ^||VariableName = $Order(@^||VariableName)
    }
    Quit ^||VariableNameList
}

}

Results:

SAMPLES>kill  set z = 0 d ##class(Demo.Variables).OuterMethod()
y,z
SAMPLES>kill  set z = 0 d ##class(Demo.Variables).NoPublicListMethod()
z

Thank you, Timothy! This is good, but what if user specify ^||VariableNameList process-private variable before calling the method? The value will be lost:

USER>set ^||VariableNameList = "TempVal" zwrite ##class(Test.Class).GetVariablesByTimothy()
$lb("Einstein","Mozart")
USER>write ^||VariableNameList
...TempVal is now gone!...

Maybe there is no absolutely save method to get the variables list without touching/rewriting other variables or globals. I hope it is enough just to name temporary variables as no one suggests to name.

Check if variable is defined beforehand and generate/use a new variable name if required:

ClassMethod GetNewVarName() As %String
{
    Set Name = "Temp"
    While $Data(@Name) {
        Set Name = Name _ $Random(100)
    }
    Return Name
}

OK :) But how to use this variable name generator? :)

set myNewVarName = ..GetNewVarName() // oops, no
 set @..GetNewVarName() = "" // now it's OK, but I need to call this function once more
 set @..GetNewVarName() = $ORDER(@@..GetNewVarName()) // oops, another variable names

Things get complicated here. I think we can stop on the solutions Michael and Timothy proposed.

Thanks!

Here's a sneaky way to use a NEWable and SETtable system variable to cache your "safe" variable name in:

 new $etrap
 set $etrap=";"_..GetNewVarName() ; semicolon prefix makes the $ETRAP into a valid line of code (i.e. a comment)
 set @$extract($etrap,2,*)=""
 set @$extract($etrap,2,*)=$order(@@$extract($etrap,2,*))

You need more than one "safe" variable? Use subscripts of the one you've grabbed:

 set @$extract($etrap,2,*)@(1)="Some more info I need to store"

As well as the suggestion of using a process-private global, Tim flags up an important point here. The $ORDER technique only enumerates public local variables.

a quick on-liner I use for debugging some times sends variabls to a global so that I can examine later:

 

s xtest="" f i=1:1 s xtest=$o(@xtest)  q:xtest=""  s ^ZZZZZ(xtest)=@xtest

 

 

Nice! Will wonder if you have some sort of more-liner, which also saves and restores objects (orefs). But I believe it is not too useful.

Hi,

After read all I didn't get exactly what you need. However, you can get all variables using ˆSPOOL.

If you run the following command, which uses the device 2 (ˆSPOOL) you will get all variables on the global ˆSPOOL

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

 

/* Writing to the ^SPOOL global */  
OPEN 2   
USE 2   
WRITE 
CLOSE 2  

/* Displaying the ^SPOOL global */  
ZW ^SPOOL

yes Old school!

P.S.

Almost entirely forgotten about spool device

Thanks, Fabio!

Good to know about SPOOL device. Thank you! However, this method touches ^SPOOL global. Being set before the call, ^SPOOL global changes it's value, which is not secure for my needs.

P.S. Recently, Stuart gave me a complete answer. Thanks for your help!

If you are really sensitive as to not defining new local variables, and having them pollute your variable list, use the variable named % (yes, just a percent sign is a valid variable name, and happens to sort first. Here is the simple case:

    WRITE:$DATA(%) !,"%"
    NEW % SET %="%" FOR SET %=$ORDER(@%) QUIT:%="" WRITE !,%

Putting that into a routine requires one more temporary variable. %0 sorts next, then %00, %000, and so on.

VARLIST() ; SRS 2016-06-30, Returns list of local variables.
    IF $DATA(%) ; This places the existence of variable % into $TEST.
    NEW % SET %=$SELECT($TEST:$LISTBUILD("%"),1:"")
    SET:$DATA(%0) %=%_$LISTBUILD("%0")
    NEW %0 SET %0="%0"
    FOR SET %0=$ORDER(@%0) QUIT:%0="" SET %=%_$LISTBUILD(%0)
    QUIT %

Many programmers consider it acceptable, if not desirable, to use variables %, and % followed by any digits for introspection only, and therefore not worry about if % and %digits are predefined, Using %, %1, %2, %3, etc, and starting loops with SET %="%9" or something like that.

Thank you,  Stuart!

I think this is a complete solution. I wrapped it to a class method and it works just as expected:

ClassMethod VarList() As %String
{
    IF $DATA(%) ; This places the existence of variable % into $TEST.
    NEW SET %=$SELECT($TEST:$LISTBUILD("%"),1:"")
    SET:$DATA(%0) %=%_$LISTBUILD("%0")
    NEW %0 SET %0="%0"
    FOR {
        SET %0=$ORDER(@%0)
        QUIT:%0=""
        SET %=%_$LISTBUILD(%0)
    }
    QUIT %
}

Results with

USER > write
%=11
a=<OBJECT REFERENCE>[1@DevProject.Robot]
i=5
USER > zw ##class(DevProject.Robot).VarList()
$lb("%","a","i")
USER > kill %
USER > zw ##class(DevProject.Robot).VarList()
$lb("a","i")