Indirection Different In Terminal Than In Method
If I open a terminal and type the following commands, sc is an error:
set validator = "sc = ##class(%Library.Numeric).IsValid(""BLAH"")"
set @validator
write scAt the end, when I write sc I get:
0 L'BLAH9
DOCXT010,#e^zIsValid+1^%Library.Numeric.1^1e^^^1However, if I call the following class method using the arguments "%Library.Numeric" and "BLAH", sc is undefined
ClassMethod testvalidator(class As %String, value As %String) As %Status{set validator = "sc = ##class("_class_").IsValid("""_value_""")"write validator,!set @validatorwrite sc,!quit sc}When it tries to write sc, I get:
write sc,! ^ <UNDEFINED>zvalidator+4^DXT4.tagvalue.1 *sc
And I can't figure out what's making that different. Why would this behave differently in this method than doing the same thing line by line in the terminal?
Comments
You have a problem with the scoping!
Indirection has a global scoping, you have to put things with indirection in a global scope:
ClassMethod testvalidator(class As %String, value As %String) As %Status [ PublicList = (validator, sc) ]
{
new validator, sc
set validator = "sc = ##class("_class_").IsValid("""_value_""")"
write validator,!
set @validator
write sc,!
quit sc
}set result = ##class(...).testvalidator("%Library.Numeric","BLABLA")
do $system.OBJ.DisplayError(result) --> ERROR #7207: Datatype value 'BLABLA' is not a valid numberThank you, Julius! It was that, and my class wasn't ProcedureBlock.
All classes are ProcedureBlock by default, you need to manually set [Not ProcedureBlock] if you don't want to compile classes to Procedures. It's highly recommended to use ProcedureBlock though.
I was updating code someone else wrote a few years ago, and they had set [Not ProcedureBlock] on the class. I'm not sure why.
As @Sergei Shutov pointed out, you can switch off the procdere block by a keyword for the whole class. Additionaly, you can switch on or off the procedure block keyword for a particular method too. In your case:
class Some.Class Extends %RegisteredObject
{
/// a procedure block method
ClassMethod ProcBlock()
{
}
/// a nonprocedurblock method
ClassMethod NoProcBlock() [ ProcedureBlock = 0 ]
{
// Caution: All variables have a global scope, hence, they will overwrite variables with the same name, which were created previously. To avoid this, use the NEW command, to protect them (if desired).
}
}Use $CLASSMETHOD and you won't have such issues:
USER><FONT COLOR="#0000ff">set </FONT><FONT COLOR="#800000">sc</FONT><FONT COLOR="#000000">=</FONT><FONT COLOR="#0000ff">$CLASSMETHOD</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#008000">"%Numeric"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#008000">"IsValid"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#008000">"BLAH"</FONT><FONT COLOR="#000000">)</FONT>
or
<FONT COLOR="#000080">ClassMethod </FONT><FONT COLOR="#000000">testvalidator(</FONT><FONT COLOR="#ff00ff">class </FONT><FONT COLOR="#000080">As %String</FONT><FONT COLOR="#000000">, </FONT><FONT COLOR="#ff00ff">value </FONT><FONT COLOR="#000080">As %String</FONT><FONT COLOR="#000000">) </FONT><FONT COLOR="#000080">As %Status </FONT><FONT COLOR="#000000">{ </FONT><FONT COLOR="#0000ff">set </FONT><FONT COLOR="#800000">sc</FONT><FONT COLOR="#000000">=</FONT><FONT COLOR="#0000ff">$CLASSMETHOD</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#800000">class</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#008000">"IsValid"</FONT><FONT COLOR="#000000">,</FONT><FONT COLOR="#800000">value</FONT><FONT COLOR="#000000">) </FONT><FONT COLOR="#0000ff">write </FONT><FONT COLOR="#800000">sc</FONT><FONT COLOR="#000000">,! </FONT><FONT COLOR="#0000ff">quit </FONT><FONT COLOR="#800000">sc </FONT><FONT COLOR="#000000">}</FONT>
He (@David Hockenbroch) is playing with inderection, using $classmethod() instead of ##class(classname).methodname(...) does not solve the scoping problem:
ClassMethodtestvalidator(classAs%String, value As%String) As%Status
{
setvalidator = "sc = $classmethod(class, ""IsValid"", value)"
writevalidator,!
set@validator
writesc,!
quitsc
}The above method gives you the same <UNDEF> error because of non global scoping! By using indirection both variables (validator and sc) must have global scope.
Well in case of $classmethod you don't need to use an indirection at all to achieve the same result. I would avoid using indirection if at all possible.
@Sergei Shutov You missed the point.
validator
is typically stored as part of the individual data record to handle various types of checks within your class to provide the highest flexibility. Think of language or geographically related checking.
You can't bypass the challenge.
Neither by $classmetho() nor any code generator as this is all static code frozen and inflexible a runtime.
Creating a check routine by field or by record is not a realistic solution
You can't bypass the challenge.
Neither by $classmetho() nor any code generator as this is all static code frozen and inflexible a runtime.
Why?
A case where user must be able to:
- create a code snippet with a predetermined interface
- choose it for execution among all the code snippets with the same interface
Can be solved in many ways, without indirection (aka executing code stored as a string).
I usually provide a base class, which a user must extend and populate with his own methods. After that in the source app, just call SubclassOf Query from %Dictionary.ClassDefinition to get all implementation and show them to a user to pick from.
Works good enough.
The question was (see the original post), why does a command work in a terminal but not in a method. And, as you know, the answer lies in the scoping. I would say, he (@David Hockenbroch) tries to learn and understand the nature of indirection and is not working on a production grade problem.
But maybe I'm wrong... who knows.
ClassMethod testvalidator(
class As %String,
value As %String) As %Status
{
set validator = "(out){set out = ##class("_class_").IsValid("""_value_""")}"
xecute (validator,.sc)
write sc,!
quit sc
}