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:
- Args ... (passing an arbitrary number of arguments to a method / procedure / program);
- XECUTE or $ XECUTE (execution of arbitrary COS commands);
- $ COMPILE (compilation / syntax checking code);
- $ CLASSMETHOD (calling an arbitrary class method with passing an arbitrary number of arguments);
- $ METHOD (calling an arbitrary method of an instance of a class with passing an arbitrary number of arguments);
- Library built-in classes .
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.myclassClass 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 objectInitialization 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 separatorFor 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.AbstractListExamples 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 numbersInitialize 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 objectsInitialization:
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!