Operator <%% and copied arrays

If someone wondered about weather arrays are in depth copied or only reference is copied: reference is copied:

[admin@ap] > { :local a ({}); :local b $a;  :local setx do={ :set $x "X"}; ($setx <%% $b); :put $a} 
x=X
[admin@ap] >

(RoS 7.16.2)

After :set b $a and manipulating $b changes $a. It proves to me that $b and $a references the same data.

Sertik mentioned a similar behaviour in More about arrays.
There is no Ros version mentioned, but it is no longer the case in Ros 7.16.2.

[admin@ap] > { :local a ({}); :local b $a;  :set ($b->"x") "X"; :put $a}                                         

[admin@ap] >

Comparing the two codes, I guess the :set ($b->“x”) makes a copy and then inserts the “x” element. But even this copy will contain references
to array type elements:

[admin@ap] > { :local a {"c"={}}; :local b $a; :set ($b->"x") "X";  :local setx do={ :set $x "X"}; ($setx <%% ($b->"c"));:put [:serialize to=json $a]}      
{"c":{"x":"X"}}
[admin@ap] >

:set ($b->“x”) makes a copy of $b array, but manipulating $b->“c” will modify $a.

In conclusion, when you need a real copy of an array do it “yourself”, making copy of each array type element:

:global cpy do={
	:if ([:typeof $1]="array") do={
		:global cpy
		:local result ( ({}),$1) ;# this makes a new array with elements from $1
		:foreach k,v in=$result do={
			:set ($result->$k) [$cpy $v]
		}
		:return $result
	} else={
		:return $1
	}
}



[admin@ap] > { :local a {"c"={}}; :local b [$cpy $a]; :set ($b->"x") "X";  :local setx do={ :set $x "X"}; ($setx <%% ($b->"c"));:put [:serialize to=json $a]} 
{"c":[]}
[admin@ap] >

To give you a use case where this matters :
There is a complex configuration described with parameters stored in an array, each element is a set of parameters which on their turn can also be arrays:

:local template {
	"isp"={ "ether1"="dhcp"; "ether2"={ "pppoe"; "pppoeusernameXXX"; "passwdYYYYY"} };
	"vlans"={ "id10"={ "ether3","ether4"} };
	"devices"={ "gw"="gateway"; "ap"="accesspoint" }
}
:local networks ({});
:set ($networks->"hq") $template
:set ($networks->"hq"->"devices"->"swServer") "switch"	;# model MMM in rack 
:set ($networks->"hq"->"devices"->"swProd") "switch"	;# model MMM at ..
:set ($networks->"hq"->"devices"->"swAcc") "switch"	;# model MMM at ..
:set ($networks->"hq"->"devices"->"swSup") "switch"	;# model MMM at ..

And it goes on and on for a long list, repeating so many times the whole selector, lots of " pairs which I frequently mess up … , it would be nice to make it more simpler, like:

:set $swPord switch ;# model MMM at ..

… in a proper context.

One coud use :set ($networks->“hq”->“devices”) ( ($networks->“hq”->“devices”),{ “swServer”=“switch” …}), but then the comments could not be placed at the end of line .

And here comes the “brilliant” idea:

{
	:local devices do={
		:set $swServer switch	;# model MMM in rack 
		:set $swProd  switch	;# model MMM at ..
		:set $swAcc  switch	;# model MMM at ..
		:set $swSup switch	;# model MMM at ..
	}
	($devices <%% ($networks->"hq"->"devices") )
}

It’s much readable, but it comes with a surprise: it also changes $template and any other item built on it.

… and I know, <%% is also undocumented. But what does “documented” mean in the RoS world?

It was a bug up to some 7.x version as @rextended mentioned in linked topic. One usually expect that variable is assigned by value, not reference, in scripting languages (as opposite in Java for eg.), and MT fixed that, so there is no need to manually create new array with copy of elements of other array.

RoS 7.18.2:

[admin@ap] > { :local a {"c"={}}; :local b $a; :set ($b->"x") "X";  :local setx do={ :set $x "X"}; ($setx <%% ($b->"c"));:put [:serialize to=json $a]}   
{"c":{"x":"X"}}
[admin@ap] >

I guess you mean the the behavior mentioned in More about arrays by Sertik or even earlier in Variable by reference documentation? (v6 BUG) by merlinthemagic and poined out by rextended at http://forum.mikrotik.com/t/variable-by-reference-documentation-v6-bug/159765/12

It is kind’of same bug, but I would not call it bug. It is just false to say :set $ makes a copy of an array.

The only way to correct inizialize empty array is [:toarray “”], any other methods do not work as expected and later give only problems.

({}) // ={} // and other frills must not be used.

http://forum.mikrotik.com/t/iterate-over-all-elements-of-an-array-of-unknown-dimension/163033/42

Then inside the function the use of x is not declared and it is also wrong to use $ in front of the variable name.
In short, it’s written with foots, it’s normal that later it gives an error or unexpected behaviours
{ :local a ({}); :local b $a; :local setx do={ :set $x “X”}; ($setx <%% $b); :put $a}
:local a [:toarray “test”]; :local b $a; :local setx do={:local x $0; :set x “X”; :put “>$x<”; :put “>$0<”}; ($setx <%% $b); :put “>$a<”; put “>$b<”
( > and < added for testing print)


It’s the same bug:
http://forum.mikrotik.com/t/what-does-op-type-do/182894/1

Does not make any difference:

[admin@ap] > { :local a {"c"=[:toarray ""]}; :local b $a; :set ($b->"x") "X";  :local setx do={ :set $x "X"}; ($setx <%% ($b->"c"));:put [:serialize to=json $a]}  
{"c":{"x":"X"}}
[admin@ap] >



Incorrect: $ in front of an undeclared variable means argument!


Here :local x means local variable. Of course it won’t affect the parameterlist $b.


Nope, has nothing to do with that.

I know very well what $ is for.
And where is the $x argument in all script except on :set ??? You pass only $0
So your error is even worse, trying to assign another value to never passed argument???

Exactly: define a new argument and pass it back to the caller! It works for more than 10 years:
RoS 6.25:

[admin@Mk] > :global fx do={:put $1; :set $3 30}
[admin@Mk] > {:local p {0;1}; ($fx <%% $p); :put $p}
1
0;1;;30
[admin@Mk]

Stating that :set a $b in case of arrays does an in-depth copy is not true. It copies the reference and in my opinion is good so, not a bug at all. But one should know about it. Precise and correct information are important.

however, as already written, the bug is the same of previous post links, already corrected on v7.something
{
:local a [:toarray “”] ; # define new empty array
:set ($a->“x”) “7” ; # on new array set x = 7
:local b $a ; # define a copy of the first array

put both 7 as expected

:put $a
:put $b
:set ($b->“x”) “8” ; # set inside the 2nd array x = 8

UNEXPECTED both x inside $a and $b now are 8

:put $a
:put $b
}
{
:local a [:toarray “”] ; # define new empty array
:set ($a->“x”) “7” ; # on new array set x = 7
:local b $a ; # define a copy of the first array

put both 7 as expected

:put $a
:put $b
:set ($b->“x”) “8” ; # set inside the 2nd array x = 8

CORRECT x inside $a is 7 and on $b is 8

:put $a
:put $b
}
{
:local a [:toarray “”] ; # define new empty array
:set ($a->“x”) “7” ; # on new array set x = 7
:local b ($a,[:toarray “”]) ; # define a copy of the first array, with bugfix

put both 7 as expected

:put $a
:put $b
:set ($b->“x”) “8” ; # set inside the 2nd array x = 8

correclty both x inside $a and $b now are 8

:put $a
:put $b
}

Whatever happens,
RouterOS is still router software, it is certainly not a reliable compilation language for creating reliable programs,
let alone with instructions that are not even documented or whose existence is not even admitted…

I repeat:

The error you mention is that :set ($b->“x”) was executed without making a copy of the array.

In terminal window, after entering ( $fx , pressing F1 presents a list. In my opinion whatever appears there is admitted a least. The “documented” part … well is ongoing work.