Код как аргумент в Caché ObjectScript

в 11:20, , рубрики: cache objectscript, intersystems, intersystems cache, Блог компании InterSystems, разработка, функциональное программирование, метки: , ,

Язык InterSystems Caché ObjectScript (COS) развивается с каждым годом (в версии 2013.1 появилась команда return, в 2012.2регулярные выражения), и в него добавляются новые команды и операторы. К сожалению, в настоящий момент подпрограммы в COS не являются объектами первого класса, то есть подпрограмму (функцию, метод) нельзя передать как параметр в подпрограмму или вернуть из подпрограммы.

Тем не менее, существуют способы смягчить эти ограничения.

Под катом рассмотрим несколько вариантов передачи кода как аргумента подпрограммы.

Пусть есть два следующих метода:

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 ##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 ##class(Sample.Company).%OpenId(rs.Get("ID"))
       
set p.Name "OOO "_p.Name
       write 
p.Name,!
       
kill p
   
}
   
kill rs
}

Для перехода на новую версию динамического SQL — %SQL.Statement нам придётся менять код в двух местах. Если бы у нас %ResultSet использовался в десяти местах, меняли бы в десяти.

Перепишем эти два метода следующим образом.

Добавим метод для обработки конкретного экземпляра Sample.Person.

ClassMethod ProcessPerson(As Sample.Person)
{
   
set p.Office "Moscow"
   
write p.Name," ",p.SSN,!
}

Вот такой командой заменим метод AllPersonsWithA:

 do ..OpenAndProcess("select ID from Sample.Person where substr(name,1,1) = 'A'","Sample.Person","ProcessPerson")

Здесь первый аргумент — это запрос, второй имя класса, экземпляры которого будут обрабатываться, третий — имя метода класса, который нужно вызывать для каждой строки результата запроса.

Для компаний метод обработки будет выглядеть так:

ClassMethod ProcessCompany(As Sample.Company)
{
   
set c.Name "OOO "_c.Name
   write 
c.Name,!
}

Вызывать будем такую команду:

 do ..OpenAndProcess("select ID from Sample.Company where substr(name,1,1) = 'O'","Sample.Company","ProcessCompany")

Теперь, собственно, сам метод OpenAndProcess:

ClassMethod OpenAndProcess(query As %StringclassName As %Stringcallback As %String)
{
  
set rs ##class(%ResultSet).%New()
  
do rs.Prepare(query)
  
do rs.Execute()
  
while rs.Next() {
      
set $classmethod(className,"%OpenId",rs.Get("ID"))
      
do $classmethod($classname(),callback,p;
      
kill p
  
}
   
kill rs
}

Функция $classmethod(class, method, arg1, arg2, ...) вызывает из класса class метод класса с именем method и передаёт ему аргументы arg1, arg2 и т.д.

Теперь, работа с %ResultSet вынесена в отдельный метод, что там делается никого не интересует.

Очевидно, метод OpenAndProcess можно исправить таким образом, чтобы он вызывал метод не из текущего, а из произвольного класса и передавал в callback произвольное число параметров.

Если код для callback совсем небольшой, можно пользоваться функцией $xecute, которая представляет собой некий аналог анонимных функций. Метод OpenAndProcess в этом случае выглядел бы как:

ClassMethod OpenAndProcess(query As %StringclassName As %Stringcallback As %String)
{
  
set rs ##class(%ResultSet).%New()
  
do rs.Prepare(query)
  
do rs.Execute()
  
while rs.Next() {
     
set $classmethod(className,"%OpenId",rs.Get("ID"))
     
set res=$xecute(callbackp)
     
kill p
  
}
  
kill rs
}

А обработка была бы заключена не в метод класса, а в строку.

 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 — формальный параметр, а значение b берётся из текущего контекста

USER>a="(in) ret in + b"

USER>b=10 w $xecute(a,2)
12

USER>b=13 w $xecute(a,2)
15

Недостатком использования $xecute для передачи кода является то, что больше нескольких команд в одну строку не поместить. Кроме того, проверка синтаксиса будет произведена только во время выполнения кода. С другой стороны нет необходимости плодить методы, которые будут использованы только один раз.

Документация:

Автор: adaptun

Источник

Поделиться

* - обязательные к заполнению поля