The language InterSystems Caché ObjectScript (COS) develops every year, new
commands and
functionality are added to it. Unfortunately, at the moment subroutines in COS are not
first-class objects , that is, a subroutine (function, method) cannot be passed as a parameter to a subroutine or returned from a subroutine.
However, there are ways to ease these restrictions.
Under the cat we consider several options for passing the code as an argument to the subroutine.
Suppose there are two of the following methods:
')
ClassMethod AllPersonsWithA ()
{
set rs = ## class ( % ResultSet ). % New ()
do rs . Prepare ( "select ID from Sample.Person where substr (name, 1,1) = 'A'" )
do rs . Execute ()
while rs . Next () {
set p = ## class ( Sample.Person ). % OpenId ( rs . Get ( "ID" ))
set p . Office = "Moscow"
write p . Name , "" , p . SSN,!
kill p
}
kill rs
}
ClassMethod AllCompaniesWithO ()
{
set rs = ## class ( % ResultSet ). % New ()
do rs . Prepare ( "select ID from Sample.Company where substr (name, 1,1) = 'O'" )
do rs . Execute ()
while rs . Next () {
set p = ## class ( Sample.Company ). % OpenId ( rs . Get ( "ID" ))
set p . Name = "OOO" _ p . Name
write p . Name,!
kill p
}
kill rs
}To switch to a new version of dynamic SQL -% SQL.Statement, we will have to change the code in two places. If we had% ResultSet used in ten places, they would change it in ten.
Rewrite these two methods as follows.
Add a method to handle a specific instance of Sample.Person.
ClassMethod ProcessPerson ( p As Sample.Person )
{
set p . Office = "Moscow"
write p . Name , "" , p . SSN,!
}Here we will replace the AllPersonsWithA method with such a command:
do .. OpenAndProcess ( "select ID from Sample.Person where substr (name, 1,1) = 'A'" , "Sample.Person" , "ProcessPerson" )Here the first argument is the request, the second name of the class whose instances will be processed, the third is the name of the class method that needs to be called for each row of the query result.
For companies, the processing method will look like this:
ClassMethod ProcessCompany ( with As Sample.Company )
{
set c . Name = "OOO" _ c . Name
write c . Name,!
}We will call the following command:
do .. OpenAndProcess ( "select ID from Sample.Company where substr (name, 1,1) = 'O'" , "Sample.Company" , "ProcessCompany" )Now, in fact, the OpenAndProcess method itself:
ClassMethod OpenAndProcess ( query As% String , className As% String , callback As% String )
{
set rs = ## class ( % ResultSet ). % New ()
do rs . Prepare ( query )
do rs . Execute ()
while rs . Next () {
set p = $ classmethod ( className , "% OpenId" , rs . Get ( "ID" ))
do $ classmethod ( $ classname (), callback , p ) ;
kill p
}
kill rs
}The function $ classmethod (class, method, arg1, arg2, ...) calls the class method from the class class with the name method and passes it the arguments arg1, arg2, etc.
Now, working with% ResultSet is in a separate method, and no one is interested in doing there.
Obviously, the OpenAndProcess method can be fixed so that it calls the method not from the current, but from an arbitrary class and passes an arbitrary number of parameters to the callback.
If the code for the callback is quite small, you can use the $ xecute function, which is an analogue of anonymous functions. The OpenAndProcess method in this case would look like:
ClassMethod OpenAndProcess ( query As% String , className As% String , callback As% String )
{
set rs = ## class ( % ResultSet ). % New ()
do rs . Prepare ( query )
do rs . Execute ()
while rs . Next () {
set p = $ classmethod ( className , "% OpenId" , rs . Get ( "ID" ))
set res = $ xecute ( callback , p )
kill p
}
kill rs
}And the processing would not be enclosed in a class method, but in a string.
set query = "select ID from Sample.Person where substr (name, 1,1) = 'A'"
do .. OpenAndProcess ( query , "Sample.Person" , "(p) s p.Office =" "Moscow" "w p.Name," "" "p.SSN,! q 0" )In brackets at the beginning of the line input parameters are specified. All other variables will be searched in the current context. In the example below, in is a formal parameter, and the value of b is taken from the current context.
USER>
s a = "(in) ret in + b"USER>
s b = 10 w $ xecute ( a , 2)12
USER>
s b = 13 w $ xecute ( a , 2)15
The disadvantage of using $ xecute to transfer code is that more than a few commands cannot be placed on one line. In addition, the syntax check will be performed only during the execution of the code. On the other hand, there is no need to produce methods that will be used only once.
Documentation: