📜 ⬆️ ⬇️

In search of an analogue of first-order functions in the Caché DBMS

The post is written in addition to the article Declarative Development on Caché .

[2, 3, 5, 7, 11, 13, 17].forEach(function(i) { console.log(i); }); 

How to do this in Caché using COS ?
Under the cut a few exercises on a given topic.

To see how COS, one of the server languages ​​built into Caché, can achieve the same concise, clear and flexible code, we developed our own class test.ForEach for working with collections-lists.
Class source code test.ForEach
/// Iterator class for collections.
Class test.ForEach Extends% RegisteredObject [ Final ]
{
/// Collection list <s> of almost </ s> of any type.
Property collection As% Collection.AbstractList [ Internal , Private , ReadOnly , Transient ];
/// Initialization of the property <property> collection </ property>.
/// <br> <br> Valid arguments <var> val </ var>:
/// <br> <li> object of the class of the heir from% Collection.AbstractList;
/// <br> <li> list of simple elements in the $ List format;
/// <br> <li> A list of simple elements in string format. In this case, <var> sep </ var> is the element separator in the string;
Method % OnNew ( val , sep = "," ) As% Status [ Private , ServerOnly = 1]
{
if $ IsObject ( val ) {
quit : ' val . % Extends ( "% Collection.AbstractList" ) $$$ ERROR ( $$$ OrefInvalid , val )
set i% collection = val
} else {
set i% collection = ## class ( % ListOfDataTypes ). % New ()
do .. collection . InsertList ( $ select ( $ listvalid ( val ): val , 1: $ listfromstring ( val , sep )))
}
quit $$$ OK
}
/// Main handler method.
/// <br>
/// <br> Arguments:
/// <br>
/// <br> <var> func </ var>: <ol> <li> class instance method name </ li> <li> class method name (any) </ li> <li> code in command format < link href = / DocBook.UI.Page.cls? KEY = RCOS_cxecute> xecute </ link> </ li> <li> some abbreviated commands </ li> </ ol>
/// Call examples: <example>
/// s obj = ## class (test.ForEach).% New ("2,3,5")
///; for each element of the collection, the corresponding class method will be invoked with passing arguments.
///; The first argument is output / input, the rest are input, but this is only a way of agreement.
///; If desired, you can swap them, make a few days off, etc.
/// d obj.Do ("className: methodName",. result, param1, param2, paramN)
///; sum of elements (makes sense only for a collection of numbers)
/// d obj.Do ("+" ,. result)
///; product (makes sense only for a collection of numbers)
/// d obj.Do ("*" ,. result)
///; concatenation with a delimiter (makes sense only for a collection of simple types)
/// d obj.Do ("_" ,. result, separator)
///; minimum (it makes sense only for a collection of simple types)
/// d obj.Do ("min",. result)
///; maximum (makes sense only for a collection of simple types)
/// d obj.Do ("max",. result)
///; average (makes sense only for a collection of numbers)
/// d obj.Do ("avg",. result)
///; any code where el = collection element, args = arguments passed
/// d obj.Do ($ lb ("s args (1,1) = args (1,1) + el") ,. result); equivalent of "+"
///; subroutine call sub ^ prog with argument passing
/// d obj.Do ($ lb ("d sub ^ prog (el, args ...)") ,. result, param1, param2, paramN)
/// </ example>
///
Method Do ( func = "+" , Args ... ) As% Status
{
#define ReturnOnError (% expr) s sc =% expr ret: $$$ ISERR ( sc ) sc

quit : '.. collection . Count () $$$ OK
')
if func = "+" {
set func = $ listbuild ( "s args (1,1) = args (1,1) + el" )
} elseif func = "*" {
set func = $ listbuild ( "s args (1,1) = args (1,1) * el" )
} elseif func = "_" {
set func = $ listbuild ( "s args (1,1) = args (1,1) _args (1,2) _el" )
} elseif func = "min" {
set func = $ listbuild ( "s: el <args (1,1) args (1,1) = el" ), Args (1) = 999999999999999
} elseif func = "max" {
set func = $ listbuild ( "s: el> args (1,1) args (1,1) = el" ), Args (1) = - 999999999999999
} elseif func = "avg" {
set func = $ listbuild ( "s args (1,1) = el / args (1,2) + args (1,1)" ), Args = 2, Args (2) = .. collection . Count () kill Args (1)
}

if $ listvalid ( func ) {
set cmd = $ list ( func )

$$$ ReturnOnError ( ## class ( % Routine ). CheckSyntax ( "" _ cmd ))

set cmd = "(el, args ...) {" _ cmd _ "}"

set key = ""
for {
set el = .. collection . GetNext (. Key )
quit : key = ""
xecute ( cmd , el ,. args )
}
} else {
if func [ ":" {
set className = $ piece ( func , ":" , 1)
set methodName = $ piece ( func , ":" , 2)
quit : ' ## class ( % Dictionary.MethodDefinition ). IDKEYExists ( className , methodName ) $$$ ERROR ( $$$ MethodDoesNotExist , func )
quit : ' $$$ defMemberKeyGet ( className , "m" , methodName , 23) $$$ ERROR ( $$$ GeneralError , $$$ FormatText ( "Method% 1 is not a method of class% 2" , methodName , className ))
set key = ""
for {
set el = .. collection . GetNext (. Key )
quit : key = ""
$$$ ReturnOnError ( $ classmethod ( className , methodName , el ,. Args ))
}
} else {
set methodName = func
set key = ""
for {
set el = .. collection . GetNext (. Key )
quit : key = ""
set className = $ classname ( el )
return : ' ## class ( % Dictionary.MethodDefinition ). IDKEYExists ( className , methodName ) $$$ ERROR ( $$$ MethodDoesNotExist , className _ ":" _ methodName )
return : $$$ defMemberKeyGet ( className , "m" , methodName , 23) $$$ ERROR ( $$$ GeneralError , $$$ FormatText ( "Method% 1 is not an instance method of class% 2" , methodName , className ))
$$$ ReturnOnError ( $ method ( el , methodName,. Args ))
}
}
}

quit $$$ OK
}
/// <example> d ## class (test.ForEach) .Test () </ example>
ClassMethod Test () [ Internal ]
{
set old = $ system .Process . Undefined (2)
try {
; ============================== COLLECTION OF SIMPLE DATA TYPES =============== ==============
set t = ## class ( test.ForEach ). % New ( "2,3,5" )
; s t = ## class (test.ForEach).% New ("2,3,5, asd")
; s t = ## class (test.ForEach).% New (## class (test.ForEach).% New ()); uncomment to see the error
if ' $ IsObject ( t ) $$$ ThrowStatus ( % objlasterror )

write !, "==========" ,!, "test.myclass: Dump" , !!!
$$$ ThrowOnError ( t . Do ( "test.myclass: Dump" ))
; or $$$ ThrowOnError (t.Do ("test.myclass: Dump",. result))

write !, "==========" ,!, "test.myclass: Dump (.r =" "result", "" p1 "", "" p2 "") " , !! !
set r = "result" $$$ ThrowOnError ( t . Do ( "test.myclass: Dump",. r , "p1" , "p2" ))

write !, "==========" ,!, "test.myclass: Sum (.r)" , !!!
$$$ ThrowOnError ( t . Do ( "test.myclass: Sum",. R )) write "Result =" , r ,!
; $$$ ThrowOnError (t.Do ("test.myclass: Sum",. R, 5)); uncomment to see the error

write !, "==========" ,!, "+10" !! set r = 10
$$$ ThrowOnError ( t . Do (,. R )) write "Result =" , r ,!
write !, "==========" ,!, "+" , !! kill r
$$$ ThrowOnError ( t . Do (,. R )) write "Result =" , r ,!
write !, "==========" ,!, "*" , !! set r = 1
$$$ ThrowOnError ( t . Do ( "*" ,. R )) write "Result =" , r ,!
write !, "==========" ,!, "_ + separator =" "^" "" !! kill r
$$$ ThrowOnError ( t . Do ( "_" ,. R , "^" )) write "Result =" , r ,!

write !, "==========" ,!, "min (input argument is not taken into account)" , !!!
set r = "asd" $$$ ThrowOnError ( t . Do ( "min" ,. r )) write "Result =" , r ,!
write !, "==========" ,!, "max (input argument is not taken into account)" , !!!
set r = "asd" $$$ ThrowOnError ( t . Do ( "max" ,. r )) write "Result =" , r ,!
write !, "==========" ,!, "avg (input argument is not taken into account)" , !!!
set r = "asd" $$$ ThrowOnError ( t . Do ( "avg" ,. r )) write "Result =" , r ,!

write !, "==========" ,!, "s args (1,1) = args (1,1) + el" , !!!
kill r $$$ ThrowOnError ( t . Do ( $ listbuild ( "s args (1,1) = args (1,1) + el" ) ,. r )) write r ,!

write !, "==========" ,!, "d sub ^ prog (el, args ...) [.r =" "r" "," "p1" "," "p2 ""] " , !!!
set r = "r" $$$ ThrowOnError ( t . Do ( $ listbuild ( "d sub ^ prog (el, args ...)" ) ,. r , "p1" , "p2" ))
; ============================== COLLECTION OF COMPLEX DATA TYPES =============== ==============
set list = ## class ( % ListOfObjects ). % New ()
for i = "f1" , "f2" , "f3" do list . Insert ( ## class ( test.myclass ). % New ( i ))
; f i = "f1", "f2", "f3", 7 d list.Insert (## class (test.myclass).% New (i))

set t = ## class ( test.ForEach ). % New ( list )
if ' $ IsObject ( t ) $$$ ThrowStatus ( % objlasterror )

write !, "++++++++++" ,!, "test.myclass: Dump" , !!!
$$$ ThrowOnError ( t . Do ( "test.myclass: Dump" ))

write !, "++++++++++" ,!, "PrintLn" , !!!
$$$ ThrowOnError ( t . Do ( "PrintLn" ))

write !, "++++++++++" ,!, "PrintLn (," "Element =" ")" , !!!
$$$ ThrowOnError ( t . Do ( "PrintLn" ,, "Element =" ))

write !, "++++++++++" ,!, "Concat (.r)" , !! kill r
$$$ ThrowOnError ( t . Do ( "Concat",. R )) write "Result =" , r ,!
; $$$ ThrowOnError (t.Do ("Concat",. R, "f3")) w "Result =", r ,! ; uncomment to see the error
write !, "++++++++++" ,!, "SetField (," blablabla "") + PrintLn (, "" Element = "") " , !!!
$$$ ThrowOnError ( t . Do ( "SetField" ,, "blablabla" )) $$$ ThrowOnError ( t . Do ( "PrintLn" ,, "Element =" ))

write !, "++++++++++" ,!, "d el.PrintLn (.args)" , !!!
$$$ ThrowOnError ( t . Do ( $ listbuild ( "d el.PrintLn (.args)" )))

write !, "++++++++++" ,!, "w" "field =" ", el.field ,!" !!!
$$$ ThrowOnError ( t . Do ( $ listbuild ( "w" "field =" ", el.field ,!" )))
} catch ( ex ) {
#dim ex As % Exception.AbstractException
write ex . DisplayString ()
}
do $ system .Process . Undefined ( old )
}
}

The class code used some of the features of COS:


Attention! All the examples below assume that Undefined = 2.

This mode can be set in the terminal.
 > set old=$system.Process.Undefined(2) 

Run tests and do not forget to return to the place
 > do $system.Process.Undefined(old) 


Class source code test.myclass
Class test.myclass Extends% RegisteredObject
{
/// String field.
Property field;
/// Initialization of the property of <property> field </ property>.
Method % OnNew ( field ) As% Status [ Internal , Private , ServerOnly = 1]
{
set .. field = field
quit $$$ OK
}
/// Filling the <property> field </ property> with the first <u> input </ u> argument.
Method SetField ( Args ... ) As% Status
{
set .. field = Args (1,2)
quit $$$ OK
}
/// Output <property> field </ property> and the first <u> input </ u> argument.
Method PrintLn ( Args ... ) As% Status
{
write Args (1,2), $$$ quote (.. field ) ,!
quit $$$ OK
}
/// Concatenation of the <property> field </ property> with a separator (<span style = "color: green;"> class </ b> instance </ b> </ span> method).
/// <br> If the first input argument matches the <var> field </ var>, generate an error (<span style = "color: red;"> for demonstration purposes! </ 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 ( "An error occurred on the element:% 1" , .. field )), 1: $$ $ OK )
}
/// Sum of <var> elem </ var> (<span style = "color: green;"> class method </ span>).
/// <br> If the first <u> input </ u> argument matches <var> elem </ var> (also known as <property> field </ property>), generate an error (<span style = "color: red; "> for demonstration purposes! </ span>)
ClassMethod Sum ( elem , Args ... ) As% Status
{
set Args (1,1) = Args (1,1) + elem
quit $ select ( elem = Args (1,2): $$$ ERROR ( $$$ GeneralError , $$$ FormatText ( "An error occurred on the element:% 1" , elem )), 1: $$$ OK )
}
/// Display all arguments.
/// <br> <br> <var> elem </ var> = collection element
/// <br> <var> Args </ var> (1) = number of arguments passed except the first, i.e. <var> elem </ var>
/// <br> <var> Args </ var> (1,1) = argument 1 (<span style = "color: red;"> we have this input / output argument </ span>)
/// <br> <var> Args </ var> (1,2) = argument 2
/// <br> ...
/// <br> <var> Args </ var> (1, n) = argument 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 "Element =" , $$$ quote ( el ), ", Output argument =" , $$$ quote ( Args (1,1)), ", Additional arguments =" , $$$ quote ( $ listtostring ( params )) ,!
quit $$$ OK
}
}

The source code of the program prog.mac
#include % systemInclude

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


Go!
Initializing the ForEach object
Initialization is performed using the test.ForEach.% New (val, sep) method.
The first parameter val takes a collection of literals, either a list or a collection of objects.
The second parameter, sep, is a separator for the literals collection.

1. Initialization of the collection of literals

 set tmp=##class(test.ForEach).%New("2,3,5")  set tmp=##class(test.ForEach).%New($listbuild(2,3,5)) 


2. Initialization of a collection of literals through an arbitrary separator
For example through the separator ";"

 set tmp=##class(test.ForEach).%New("2;zxc;5;asd,ert",";")  set tmp=##class(test.ForEach).%New($listbuild(2,"zxc",5,"asd,ert")) 


3. Initializing the object list
 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) 


Attention! The test.ForEach class in the% New method expects a descendant collection from % Collection.AbstractList

Examples of using


The test.myclass class implements several methods that we will call for each of the elements of the collection.
For example, Dump - displays information about the item and the parameters passed.
Sum - summarizes the arguments, displays the result.

Examples with a collection of numbers
Initialize the collection:
 set tmp=##class(test.ForEach).%New("2,3,5") 


Perform in the terminal:
 >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" 


Other examples with numbers

 >kill r do tmp.Do("test.myclass:Sum",.r) write r 10 >kill r 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(,.r) write r 20 (=10 +2+3+5) >kill r do tmp.Do(,.r) write r 10 (=2+3+5) >set r=-10 do tmp.Do("+",.r) write r 0 (=-10 +2+3+5) >set r=1 do tmp.Do("*",.r) write r 30 (=2*3*5) >kill r do tmp.Do("_",.r,"^") write r ^2^3^5 (  ) >do tmp.Do("min",.r) write r 2 () >do tmp.Do("max",.r) write r 5 () >do tmp.Do("avg",.r) write r 3.333333333333333334 (=(2+3+5)/3) >kill r do tmp.Do($listbuild("set args(1,1)=args(1,1)+el"),.r) write 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:  ! 



Examples of use for the collection of objects

Initialization:
 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) 


Check in terminal:
 >do tmp.Do("test.myclass:Dump")  = "f1",   = "",   = ""  = "f2",   = "",   = ""  = "f3",   = "",   = "" >do tmp.Do("PrintLn") "f1" "f2" "f3" >do tmp.Do("PrintLn",," = ")  = "f1"  = "f2"  = "f3" >kill r do tmp.Do("Concat",.r,"**") write r **f1**f2**f3 >kill r 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 


Other types of collections were ignored, for example: arrays, globals, tables, and streams. But now you know “how it works” ...

Sources of classes and examples.

Disclaimer : This article was published with the permission of the author who wished to remain anonymous.

Thanks for attention!

Source: https://habr.com/ru/post/264983/


All Articles