Язык 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 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
}
Для перехода на новую версию динамического SQL — %SQL.Statement нам придётся менять код в двух местах. Если бы у нас %ResultSet использовался в десяти местах, меняли бы в десяти.
Перепишем эти два метода следующим образом.
Добавим метод для обработки конкретного экземпляра Sample.Person.
ClassMethod ProcessPerson(p 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(c 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 %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
}
Функция $classmethod(class, method, arg1, arg2, ...) вызывает из класса class метод класса с именем method и передаёт ему аргументы arg1, arg2 и т.д.
Теперь, работа с %ResultSet вынесена в отдельный метод, что там делается никого не интересует.
Очевидно, метод OpenAndProcess можно исправить таким образом, чтобы он вызывал метод не из текущего, а из произвольного класса и передавал в callback произвольное число параметров.
Если код для callback совсем небольшой, можно пользоваться функцией $xecute, которая представляет собой некий аналог анонимных функций. Метод OpenAndProcess в этом случае выглядел бы как:
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
}
А обработка была бы заключена не в метод класса, а в строку.
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>s a="(in) ret in + b"
USER>s b=10 w $xecute(a,2)
12
USER>s b=13 w $xecute(a,2)
15
Недостатком использования $xecute для передачи кода является то, что больше нескольких команд в одну строку не поместить. Кроме того, проверка синтаксиса будет произведена только во время выполнения кода. С другой стороны нет необходимости плодить методы, которые будут использованы только один раз.
Документация:
Автор: adaptun