Блог

    Caché (Кашэ́) — промышленная высокопроизводительная, объектная система управления базами данных, интегрированная с технологией разработки веб-приложений. Единая архитектура данных Caché позволяет разработчикам использовать одновременно объектный, реляционный (SQL) и прямой (NoSQL) доступ к одним и тем же данным, хранение которых обеспечивается ориентированным на транзакции многомерным ядром СУБД.

    http://www.intersystems.ru/cache/

Последние записи


Теги

Информация

В поисках аналога функций первого порядка в СУБД Caché

добавлено: 03 апр 15
понравилось:0
просмотров: 773
комментов: 0

теги:

Автор: servit

Пост написан в дополнение к статье на хабре и местному обсуждению темы: 17450618.

Википедия
В информатике язык программирования имеет функции первого класса, если он рассматривает функции как объекты первого класса. В частности, это означает, что язык поддерживает передачу функций в качестве аргументов другим функциям, возврат их как результат других функций, присваивание их переменным или сохранение в структурах данных. Некоторые теоретики языков программирования считают необходимым условием также поддержку анонимных функций. В языках с функциями первого класса имена функций не имеют никакого специального статуса, они рассматриваются как обычные значения, тип которых является функциональным.

источник
СУБД Caché не поддерживает функции первого порядка. Это приговор? Нет, и это хорошо выразил П.С.М.:
П.С.М.
jxcoder
В Cache нет достаточно многих вещей, которые привычны разработчику, который привык писать на таких языках как Java / C++.
Те "многие вещи" появились в тех языках не от хорошей жизни. Суть их сводится, как правило, к борьбе с ограничениями языков накладываемых из-за типизированности последних. В Mumps (прямым потомком которого есть COS, с его классовой надстройкой) изначально типов нет (точнее есть, но только один - всё есть строка), соответственно тут просто являются не нужными те "многие вещи" которые есть в типизированных языках. Конечно, с классами каше ситуация немного меняется, т.к. искусственно вводятся типы, но все равно не стоит забывать, что изначально всё есть строка. И прежде чем переносить реализацию какого-либо подхода из какого-либо типизированного языка, стоит подумать о целесообразности реализации точно такого же подхода в нетипизированном языке, для чего стоит вспомнить, для решения каких задач и преодоления каких ограничений в том языке, данный подход создавался.
[2, 3, 5, 7, 11, 13, 17].forEach(function(i) {
  console.log(i);
});
Чтобы увидеть как средствами COS - одного из серверных языков, встроенных в Caché - можно добиться такого же лаконичного, наглядного и гибкого кода, был разработан собственный класс test.ForEach для работы с коллекциями-списками.
Для этого были задействованы следующие возможности COS:

  • Args... (передача произвольного числа аргументов в метод/процедуру/программу);
  • XECUTE или $XECUTE (выполнение произвольных команд COS);
  • $COMPILE (компиляция/проверка синтаксиса кода);
  • $CLASSMETHOD (вызов произвольного метода класса с передачей произвольного числа аргументов);
  • $METHOD (вызов произвольного метода экземпляра класса с передачей произвольного числа аргументов);
  • Богатый набор встроенных классов, любезно предоставленный компанией InterSystems.
Код с исходниками и всеми примерами с описанием, доступным через Caché Documatic - в конце поста.
Здесь же рассмотрим лишь код вызова и результаты.
Примечание:
Все примеры предполагают Undefined=2.
Для примеров будем использовать следующий класс:
+ Класс test.myclass
Class test.myclass Extends %RegisteredObject
{

/// Строковое поле.
Property 
field;

/// Инициализация свойства <property>field</property>.
Method 
%OnNew(fieldAs %Status InternalPrivateServerOnly = 1 ]
{
  
set ..field=field
  
quit $$$OK
}

/// Заполнение <property>field</property> первым <u>входным</u> аргументом.
Method 
SetField(Args...As %Status
{
  
set ..field=Args(1,2)
  
quit $$$OK
}

/// Вывод <property>field</property> и первого <u>входного</u> аргумента.
Method 
PrintLn(Args...As %Status
{
  
write Args(1,2),$$$quote(..field),!
  
quit $$$OK
}

/// Конкатенация <property>field</property> с разделителем (<span style="color:green;">метод <b>экземпляра</b> класса</span>).
/// <br>Если первый входной аргумент совпадает с <var>field</var>, генерируем ошибку (<span style="color:red;">для демонстрационных целей!</span>)
Method 
Concat(Args...As %Status
{
  
set Args(1,1)=Args(1,1)_Args(1,2)_..field
  quit $select
(..field=Args(1,2):$$$ERROR($$$GeneralError,$$$FormatText("Возникла ошибка на элементе: %1",..field)),1:$$$OK)
}

/// Сумма <var>elem</var> (<span style="color:green;">метод класса</span>).
/// <br>Если первый <u>входной</u> аргумент совпадает с <var>elem</var> (он же <property>field</property>), генерируем ошибку (<span style="color:red;">для демонстрационных целей!</span>)
ClassMethod 
Sum(
  
elem,
  
Args...As %Status
{
  
set Args(1,1)=Args(1,1)+elem
  
quit $select(elem=Args(1,2):$$$ERROR($$$GeneralError,$$$FormatText("Возникла ошибка на элементе: %1",elem)),1:$$$OK)
}

/// Вывод всех аргументов.
/// <br><br> <var>elem</var> = элемент коллекции
/// <br> <var>Args</var>(1) = кол-во переданных аргументов кроме первого, т.е. <var>elem</var>
/// <br> <var>Args</var>(1,1) = аргумент 1 (<span style="color:red;">у нас это входной/выходной аргумент</span>)
/// <br> <var>Args</var>(1,2) = аргумент 2
/// <br> ...
/// <br> <var>Args</var>(1,n) = аргумент n
ClassMethod 
Dump(
  
elem,
  
Args...As %Status
{
  
set params=""
  
  
for i=2:1:Args(1)  set params=params_$listbuild(Args(1,i))
  
if '$IsObject(elem{
    
set el=elem
  
}elseif elem.%Extends("test.myclass"){
    
set el=elem.field
  
}else{
    
set el=elem.%ClassName($$$YES)
  
}
  
write "Элемент = ",$$$quote(el),", Выходной аргумент = ",$$$quote(Args(1,1)),", Дополнительные аргументы = ",$$$quote($listtostring(params)),!
  
quit $$$OK
}

}
и программу "prog.mac":
Макро-программа prog.mac
#include %systemInclude

sub(el,args...) public {
  
write "--------",!,"el = ",$$$quote(el),!
  
zwrite args
  
write !
}

Поехали.

Инициализация объекта ForEach
set tmp=##class(test.ForEach).%New("2;zxc;5;asd,ert",";")
или
set tmp=##class(test.ForEach).%New($listbuild(2,"zxc",5,"asd,ert"))
set tmp=##class(test.ForEach).%New("2,3,5")
или
set tmp=##class(test.ForEach).%New($listbuild(2,3,5))
set list=##class(%ListOfObjects).%New()
for i="f1","f2","f3",7 do list.Insert(##class(test.myclass).%New(i))

set tmp=##class(test.ForEach).%New(list)
Если передать объект класса-не наследника от %Collection.AbstractList, то будет ошибка, которую нужно не забыть обработать:

>set tmp=##class(test.ForEach).%New(##class(test.ForEach).%New())
>do:'$IsObject(tmp$system.OBJ.DisplayError(%objlasterror)

ОШИБКА #5807: Недопустимый OREF '1@test.ForEach'
Примеры использования для коллекции чисел

Предполагается, что:
set tmp=##class(test.ForEach).%New("2,3,5")
>do tmp.Do("test.myclass:Dump")
Элемент = 2, Выходной аргумент = "", Дополнительные аргументы = ""
Элемент = 3, Выходной аргумент = "", Дополнительные аргументы = ""
Элемент = 5, Выходной аргумент = "", Дополнительные аргументы = ""

>set r="result" do tmp.Do("test.myclass:Dump",.r,"p1","p2")
Элемент = 2, Выходной аргумент = "result", Дополнительные аргументы = "p1,p2"
Элемент = 3, Выходной аргумент = "result", Дополнительные аргументы = "p1,p2"
Элемент = 5, Выходной аргумент = "result", Дополнительные аргументы = "p1,p2"

>kill do tmp.Do("test.myclass:Sum",.rwrite r
10

>kill do $system.OBJ.DisplayError(tmp.Do("test.myclass:Sum",.r,5))
ОШИБКА #5001: Возникла ошибка на элементе: 5

>do $system.OBJ.DisplayError(tmp.Do("PrintLn"))
ОШИБКА #5654: Метод '2:PrintLn' не существует

>do $system.OBJ.DisplayError(tmp.Do("test.myclass:PrintLn"))
ОШИБКА #5001: Метод PrintLn не является методом класса test.myclass

>set r=10 do tmp.Do(,.rwrite r
20 (=10 +2+3+5)

>kill do tmp.Do(,.rwrite r
10 (=2+3+5)

>set r=-10 do tmp.Do("+",.rwrite r
0 (=-10 +2+3+5)

>set r=1 do tmp.Do("*",.rwrite r
30 (=2*3*5)

>kill do tmp.Do("_",.r,"^"write r
^2^3^5 (склейка с разделителем)

>do tmp.Do("min",.rwrite r
2 (минимум)

>do tmp.Do("max",.rwrite r
5 (максимум)

>do tmp.Do("avg",.rwrite r
3.333333333333333334 (=(2+3+5)/3)
Примечание:
Для команд "min/max/avg" значение входного/выходного аргумента не учитывается.
>kill do tmp.Do($listbuild("set args(1,1)=args(1,1)+el"),.rwrite r
10 (=2+3+5)

>set r="r" do tmp.Do($listbuild("do sub^prog(el,args...)"),.r,"p1","p2")
--------
el = 2
args=1
args(1)=3
args(1,1)="r"
args(1,2)="p1"
args(1,3)="p2"

--------
el = 3
args=1
args(1)=3
args(1,1)="r"
args(1,2)="p1"
args(1,3)="p2"

--------
el = 5
args=1
args(1)=3
args(1,1)="r"
args(1,2)="p1"
args(1,3)="p2"

>set r="r" do tmp.Do($listbuild("do1 sub^prog(el,args...)"),.r,"p1","p2")
ОШИБКА #5745: Ошибка компиляции!

Примеры использования для коллекции объектов класса test.myclass

Предполагается, что:
set list=##class(%ListOfObjects).%New()
for i="f1","f2","f3" do list.Insert(##class(test.myclass).%New(i))
set tmp=##class(test.ForEach).%New(list)
>do tmp.Do("test.myclass:Dump")
Элемент = "f1", Выходной аргумент = "", Дополнительные аргументы = ""
Элемент = "f2", Выходной аргумент = "", Дополнительные аргументы = ""
Элемент = "f3", Выходной аргумент = "", Дополнительные аргументы = ""

>do tmp.Do("PrintLn")
"f1"
"f2"
"f3"

>do tmp.Do("PrintLn",,"Элемент = ")
Элемент = "f1"
Элемент = "f2"
Элемент = "f3"

>kill do tmp.Do("Concat",.r,"**"write r
**f1**f2**f3

>kill do $system.OBJ.DisplayError(tmp.Do("Concat",.r,"f3"))
ОШИБКА #5001: Возникла ошибка на элементе: f3

>do $system.OBJ.DisplayError(tmp.Do("PrintLn1"))
ОШИБКА #5654: Метод 'test.myclass:PrintLn1' не существует

>do $system.OBJ.DisplayError(tmp.Do("Sum",.r))
ОШИБКА #5001: Метод Sum не является методом экземпляра класса test.myclass

>do tmp.Do("SetField",,"blablabla"), tmp.Do("PrintLn",,"Элемент = ")
Элемент = "blablabla"
Элемент = "blablabla"
Элемент = "blablabla"

>do tmp.Do($listbuild("do el.PrintLn(.args)"))
"blablabla"
"blablabla"
"blablabla"

>do tmp.Do($listbuild("write ""field="",el.field,!"))
field=blablabla
field=blablabla
field=blablabla

Без внимания остались другие типы коллекций, например: массивы, глобалы, таблицы, потоки. Но зато теперь вы знаете "как это работает"...

Улучшайте, дополняйте, исправляйте класс test.ForEach для ваших нужд.

Надеюсь, немного перефразировав пользователя хабра PositiveAlex,
PositiveAlex
"после такого, подобный chache код не становитсяет каким-то триллером или фильмом ужасов".
ForEach.xml

Комментарии




Необходимо войти на сайт, чтобы оставлять комментарии