Community discussions

MikroTik App
 
sin3vil
newbie
Topic Author
Posts: 34
Joined: Sat May 26, 2018 10:05 pm

local dictionary variable persisting between runs

Fri Jan 29, 2021 5:33 pm

Hello all,

Can someone explain how this is possible?
[kntouskos@sini2] > global test do={ local a ({});set ($a->"$1") 1;put $a;set a;put $a}
[kntouskos@sini2] > $test a
a=1

[kntouskos@sini2] > $test b
a=1;b=1

[kntouskos@sini2] > $test c
a=1;b=1;c=1
 
sin3vil
newbie
Topic Author
Posts: 34
Joined: Sat May 26, 2018 10:05 pm

Re: local dictionary variable persisting between runs

Mon Feb 01, 2021 7:50 pm

Shamelessly bumping this.
 
User avatar
Jotne
Forum Guru
Forum Guru
Posts: 3291
Joined: Sat Dec 24, 2016 11:17 am
Location: Magrathean

Re: local dictionary variable persisting between runs

Tue Feb 02, 2021 8:29 am

Not sure what you try to do. What is your goal?

Have a look at the variable test after each run and you see that it will be updated. So you are actually editing the test variable/script for each run.
You are adding new kv pair to the variable value within the script.

1 run.
;(eval / (eval /localname=$a;value=) (eval /setname=(-> $a $1);value=1) (eval /putmessage=$a) (eval /setname=$a) (eval /putmessage=$a))
After $test a
;(eval / (eval /localname=$a;value=a=1) (eval /setname=(-> $a $1);value=1) (eval /putmessage=$a) (eval /setname=$a) (eval /putmessage=$a))
After $test b
;(eval / (eval /localname=$a;value=a=1;b=1) (eval /setname=(-> $a $1);value=1) (eval /putmessage=$a) (eval /setname=$a) (eval /putmessage=$a))
After $test c
;(eval / (eval /localname=$a;value=a=1;b=1;c=1) (eval /setname=(-> $a $1);value=1) (eval /putmessage=$a) (eval /setname=$a) (eval /putmessage=$a))
After $test Hello
;(eval / (eval /localname=$a;value=Hello=1;a=1;b=1;c=1) (eval /setname=(-> $a $1);value=1) (eval /putmessage=$a) (eval /setname=$a) (eval /putmessage=$a))
 
sin3vil
newbie
Topic Author
Posts: 34
Joined: Sat May 26, 2018 10:05 pm

Re: local dictionary variable persisting between runs

Tue Feb 02, 2021 12:48 pm

Hello Jotne,

I'm expecting that when using a local variable that the value will be empty in the next run or when I unset it.
I'm not at any point modifying "test" function.

Running on the prompt, behavior is as expected.
[kntouskos@sini2] > local a ({});set ($a->"a") 1;put $a;set ($a->"b") 1;put $a;set ($a->"hello") 1;put $a;set a;put $a;local a ({});set ($a->"whatsallthisthen") 1;put $a                  
a=1
a=1;b=1
a=1;b=1;hello=1

whatsallthisthen=1
[kntouskos@sini2] > local a ({});set ($a->"a") 1;put $a;set ($a->"b") 1;put $a;set ($a->"hello") 1;put $a;set a;put $a;local a ({});set ($a->"whatsallthisthen") 1;put $a
a=1
a=1;b=1
a=1;b=1;hello=1

whatsallthisthen=1
Once it get's wrapped into a function it breaks.
[kntouskos@sini2] > global test do={local a ({});set ($a->"a") 1;put $a;set ($a->"b") 1;put $a;set ($a->"hello") 1;put $a;set a;put $a;local a ({});set ($a->"whatsallthisthen") 1;put $a }
[kntouskos@sini2] > $test
a=1
a=1;b=1
a=1;b=1;hello=1

whatsallthisthen=1
[kntouskos@sini2] > $test #second run
a=1;b=1;hello=1
a=1;b=1;hello=1
a=1;b=1;hello=1

whatsallthisthen=1
Is this normal behavior?

What I'm actually trying to do is somethng like this.
I have a function that returns a dictionary based on preset values, depending on what's requested. Not all entries have the same keys present.
For example, a directory listing with name, phones, addresses and other info.
Bob has a name, address and mobile number, Tom doesn't have an address but has a birthday. Running this returns bad results after the second run.

function :
global main do={

local dict ({})

#populate dict with values based on given name
if ($1="bob") do={
set ($dict->"name") "bob"
set ($dict->"address") "maple st."
set ($dict->"mobile") "5551234"
}

if ($1="tom") do={
set ($dict->"name") "tom"
set ($dict->"mobile") "main ave."
set ($dict->"birthday") "01011970"
}

return $dict
}
execution :
[kntouskos@sini2] > #tom has a bday
[kntouskos@sini2] > foreach key,value in=[$main tom] do={ put ("$key is $value") }
address is maple st.
birthday is 01011970
mobile is main ave.
name is tom
[kntouskos@sini2] > #bob doesnt have a bday
[kntouskos@sini2] > foreach key,value in=[$main bob] do={ put ("$key is $value") }
address is maple st.
birthday is 01011970
mobile is 5551234
name is bob
edit:

I've already fixed my script by using two arrays instead of a dictionary and looping over the "key" array in the foreach loop and using the index to grab the value from the "value" array, but using a dictionary looks much more elegant. Plus this left me dumbfound, so I really want to know whats up and why it's happening.
 
sin3vil
newbie
Topic Author
Posts: 34
Joined: Sat May 26, 2018 10:05 pm

Re: local dictionary variable persisting between runs

Tue Feb 09, 2021 6:02 pm

bump.
 
User avatar
Jotne
Forum Guru
Forum Guru
Posts: 3291
Joined: Sat Dec 24, 2016 11:17 am
Location: Magrathean

Re: local dictionary variable persisting between runs

Tue Feb 09, 2021 10:54 pm

What I'm actually trying to do is somethng like this.
I have a function that returns a dictionary based on preset values, depending on what's requested. Not all entries have the same keys present.
For example, a directory listing with name, phones, addresses and other info.
Bob has a name, address and mobile number, Tom doesn't have an address but has a birthday. Running this returns bad results after the second run.
I do not understand what you try to do and what the above have to do with RouterOS.
Can you not post what the real goal you have?
 
sin3vil
newbie
Topic Author
Posts: 34
Joined: Sat May 26, 2018 10:05 pm

Re: local dictionary variable persisting between runs

Wed Feb 10, 2021 10:39 am

Sorry Jotne,

I'll try to be more clear.

I'm using a local variable to hold a dictionary. I'm expecting that local variable to be empty next time around, not hold values from previous runs.
I'm not interested in "doing" something. I've already done it. What I want is to understand why a local variable retains it's values, if this is intended (and why) or if this is a bug.

If you really really need a script, consider this from my previous post.
I have a global function that acts as a directory.
Within it I have names, emails, phones and maybe other info. Not all entries need to have the same keys.
Each time I request something I expect to get the proper values.
It's not about if the script is optimal, makes sense, can be written in a different way or whatever, it's about not producing the expected results and why.


script:
global main do={

local dict ({})

#populate dict with values based on given name
if ($1="bob") do={
set ($dict->"name") "bob"
set ($dict->"address") "maple st."
set ($dict->"mobile") "5551234"
}

if ($1="tom") do={
set ($dict->"name") "tom"
set ($dict->"mobile") "666"
set ($dict->"birthday") "01011970"
}

return $dict
}

results:
[kntouskos@sini2] > foreach key,value in=[$main bob] do={ put ("$key is $value") } #requesting dictionary for bob, as this is the first run results are correct.
address is maple st.
mobile is 5551234
name is bob
[kntouskos@sini2] > foreach key,value in=[$main tom] do={ put ("$key is $value") } #requesting dictionary for tom, results are incorrect
address is maple st. #tom has no address key, we're getting bobs
birthday is 01011970
mobile is 666
name is tom
[kntouskos@sini2] > foreach key,value in=[$main bob] do={ put ("$key is $value") } #3rd run, requesting bob again
address is maple st.
birthday is 01011970 #bob has no birdthday, we're getting toms
mobile is 5551234
name is bob


I do understand from your previous post that the function is getting re-written by each iteration, the question is why and should it.
[kntouskos@sini2] > put $main
;(eval / (eval /localname=$dict;value=address=maple st.;birthday=01011970;mobile=5551234;name=bob) (eval /ifcondition=(= $1 bob);do=;(eval / (eval /setname=(-> $dict name);value=bob) (eval /setname=(-> $dict address);value=maple st.) (eval /setname=(-> $dict mobile);value=5551234))) (eval /ifcondition=(= $1 tom);do=;(eval / (eval /setname=(-> $dict name);value=tom) (eval /setname=(-> $dict mobile);value=main ave.) (eval /setname=(-> $dict birthday);value=01011970))) (eval /returnvalue=$dict))
 
User avatar
Jotne
Forum Guru
Forum Guru
Posts: 3291
Joined: Sat Dec 24, 2016 11:17 am
Location: Magrathean

Re: local dictionary variable persisting between runs

Wed Feb 10, 2021 10:53 am

Why do you like to store this on the router? Why not use a Radius server.
 
sid5632
Long time Member
Long time Member
Posts: 553
Joined: Fri Feb 17, 2017 6:05 pm

Re: local dictionary variable persisting between runs

Wed Feb 10, 2021 6:19 pm

Why do you like to store this on the router?
That's irrelevant. Just answer the question as posed. Don't try and tell someone to do something else.
 
User avatar
Jotne
Forum Guru
Forum Guru
Posts: 3291
Joined: Sat Dec 24, 2016 11:17 am
Location: Magrathean

Re: local dictionary variable persisting between runs

Wed Feb 10, 2021 9:57 pm

Its not irrelevant. To solve problem its good to have as much information as possible. There even may be an other much better solution that OP has thought of
 
2frogs
Forum Veteran
Forum Veteran
Posts: 713
Joined: Fri Dec 03, 2010 1:38 am

Re: local dictionary variable persisting between runs

Thu Feb 11, 2021 6:25 am

You can narrow the scope for the local variable to help mitigate the issue:
global main do={
#populate dict with values based on given name
if ($1="bob") do={
local dict ({})
set ($dict->"name") "bob"
set ($dict->"address") "maple st."
set ($dict->"mobile") "5551234"
return $dict
}
if ($1="tom") do={
local dict ({})
set ($dict->"name") "tom"
set ($dict->"mobile") "666"
set ($dict->"birthday") "01011970"
return $dict
}
}

foreach key,value in=[$main bob] do={ put ("$key is $value") }
address is maple st.
mobile is 5551234
name is bob

foreach key,value in=[$main tom] do={ put ("$key is $value") } 
birthday is 01011970
mobile is 666
name is tom
 
sid5632
Long time Member
Long time Member
Posts: 553
Joined: Fri Feb 17, 2017 6:05 pm

Re: local dictionary variable persisting between runs

Thu Feb 11, 2021 2:24 pm

Its not irrelevant. To solve problem its good to have as much information as possible.
It IS irrelevant. The problem is to do with scripting and variables not any other use they may be put to. Answer the question as posed or don't bother answering at all.
 
Emil66
Frequent Visitor
Frequent Visitor
Posts: 62
Joined: Tue Aug 28, 2018 2:09 am

Re: local dictionary variable persisting between runs  [SOLVED]

Fri Feb 12, 2021 2:10 am

Mikrotik RouterOS script variables are "static". The value is stored with the function, and as you have seen, this is implemented by rewriting the function whenever the value of a local variable is changed. Assigning an array is done by reference, not by value, which means the variable doesn't get its own copy of the array but uses the same storage as the original array. This can lead to very counter-intuitive situations, for example:
> :global test do={:local empty ({}); :put $empty; :local a $empty; :set ($a->"foo") 1; :put $a}
> $test

foo=1
> $test
foo=1
foo=1
> :put $test
;(eval (eval /localname=$empty;value=foo=1) (eval /putmessage=$empty) (eval /localname=$a;value=$empty) (eval /setname=(-> $a foo);value=1) (eval /putmessage=$a))
The interpreter has put the assigned value into the $empty variable, because $a is just another reference to the original array referenced by $empty. The function is rewritten to remember the value, so the next time the function is called, $empty starts with the value it was assigned during the previous execution.

The way around this is to create a new array and assign it to the variable. But you can't use a literal or the interpreter is going to rewrite it to remember the value. So use a function which returns an array:
> :global test do={:local a [:toarray ""]; :put $a; :set ($a->"foo") 1; :put $a}
> $test

foo=1
> $test

foo=1
> :put $test
;(eval (eval /localname=$a;value=(eval (eval /toarrayvalue=))) (eval /putmessage=$a) (eval /setname=(-> $a foo);value=1) (eval /putmessage=$a))
Voila, without the array literal, the interpreter has no place to remember the assigned value, and the next invocation of the function starts with an empty array.
 
sin3vil
newbie
Topic Author
Posts: 34
Joined: Sat May 26, 2018 10:05 pm

Re: local dictionary variable persisting between runs

Fri Feb 12, 2021 7:57 pm

Its not irrelevant. To solve problem its good to have as much information as possible.
It IS irrelevant. The problem is to do with scripting and variables not any other use they may be put to. Answer the question as posed or don't bother answering at all.

:D

You can narrow the scope for the local variable to help mitigate the issue:
global main do={
#populate dict with values based on given name
if ($1="bob") do={
local dict ({})
set ($dict->"name") "bob"
set ($dict->"address") "maple st."
set ($dict->"mobile") "5551234"
return $dict
}
if ($1="tom") do={
local dict ({})
set ($dict->"name") "tom"
set ($dict->"mobile") "666"
set ($dict->"birthday") "01011970"
return $dict
}
}
foreach key,value in=[$main bob] do={ put ("$key is $value") }
address is maple st.
mobile is 5551234
name is bob

foreach key,value in=[$main tom] do={ put ("$key is $value") } 
birthday is 01011970
mobile is 666
name is tom

Thanks for the recommendation 2frogs, though I believe Emil66's response takes care of it in a better way.

Mikrotik RouterOS script variables are "static". The value is stored with the function, and as you have seen, this is implemented by rewriting the function whenever the value of a local variable is changed. Assigning an array is done by reference, not by value, which means the variable doesn't get its own copy of the array but uses the same storage as the original array. This can lead to very counter-intuitive situations, for example:
> :global test do={:local empty ({}); :put $empty; :local a $empty; :set ($a->"foo") 1; :put $a}
> $test

foo=1
> $test
foo=1
foo=1
> :put $test
;(eval (eval /localname=$empty;value=foo=1) (eval /putmessage=$empty) (eval /localname=$a;value=$empty) (eval /setname=(-> $a foo);value=1) (eval /putmessage=$a))
The interpreter has put the assigned value into the $empty variable, because $a is just another reference to the original array referenced by $empty. The function is rewritten to remember the value, so the next time the function is called, $empty starts with the value it was assigned during the previous execution.

The way around this is to create a new array and assign it to the variable. But you can't use a literal or the interpreter is going to rewrite it to remember the value. So use a function which returns an array:
> :global test do={:local a [:toarray ""]; :put $a; :set ($a->"foo") 1; :put $a}
> $test

foo=1
> $test

foo=1
> :put $test
;(eval (eval /localname=$a;value=(eval (eval /toarrayvalue=))) (eval /putmessage=$a) (eval /setname=(-> $a foo);value=1) (eval /putmessage=$a))
Voila, without the array literal, the interpreter has no place to remember the assigned value, and the next invocation of the function starts with an empty array.

Great post Emil66. Thanks a lot for the explanation, though tbh I'm not sure I understand exactly why it's happening. I do understand however that ({}) is not the proper way to initialize an array but rather use [toarray ""].

edit: the question now is, is this a feature, a known limitation or a bug and should it be reported to MT support?
 
Emil66
Frequent Visitor
Frequent Visitor
Posts: 62
Joined: Tue Aug 28, 2018 2:09 am

Re: local dictionary variable persisting between runs

Fri Feb 12, 2021 8:47 pm

is this a feature, a known limitation or a bug
It's a choice: https://en.wikipedia.org/wiki/Local_var ... _variables
It's a bit weird that scalar variables are not static and arrays are, but the script language is all sorts of weird anyway.

I do understand however that ({}) is not the proper way to initialize an array but rather use [toarray ""].
It is not wrong to initialize with a literal. It just means the interpreter will remember the variable value between executions of the function. Initializing the variable with a fresh empty array from the return value of [:toarray ""] is just a way to use static variables like they aren't static. And btw., if you use [:toarray ({})], no conversion is necessary and the return value is just a reference to that literal. Then the interpreter once again knows where to remember the array value.

Who is online

Users browsing this forum: veskojl and 20 guests