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
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!
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:It works awesome until user specify variables named
UndefinedSpecialList
,UndefinedSpecialInt
andUndefined
. 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:
Thank you, Timothy! This is good, but what if user specify
^||VariableNameList
process-private variable before calling the method? The value will be lost: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? :)
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:
You need more than one "safe" variable? Use subscripts of the one you've grabbed:
Tricky. Good to know, thanks!
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=...
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:
Putting that into a routine requires one more temporary variable. %0 sorts next, then %00, %000, and so on.
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:
{
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
%=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")