|
Derar Novice
Joined: 09 Sep 2006 Posts: 44
|
Posted: Mon Aug 02, 2010 12:33 am
[3.22] User-Defined Functions: arglist acting as By-Reference for lists |
I'm not going to cry bug on this one out of the gate, since it may be intentional behaviour, and it also sort of acts as a viable way to accomplish the behaviour, that's probably easier than other methods.
Since the list changes, user-defined functions with pre-defined arguments set up are treating lists assigned to those args as by reference, rather than creating a copy of the var local to that function block. The same is not true of %XX parameter reference; this continues to create copies. Consequently, changes made to the arglist local variable are also made to the list variable passed into the function. This only happens with json table vars, that I can see so far (so, list & DB vars).
Consider the following (spaced for reading):
Code: |
<func name="_TestFunc1" id="2">
<value>
$list = %delnitem($list, 1)
#RETURN $list
</value>
<arglist>$list</arglist>
</func>
<func name="_TestFunc2" id="3">
<value>
$list = %1
$list = %delnitem($list, 1)
#RETURN $list
</value>
</func>
<func name="_TestFunc3" id="7">
<value>
$list = %additem("Added by Function", $list)
#RETURN $list
</value>
<arglist>$list</arglist>
</func>
<func name="_TestFunc4" id="13">
<value>
$var = %concat($var, " (This added by function.)")
#RETURN $var
</value>
<arglist>$var</arglist>
</func>
<func name="_TestFunc5" id="20">
<value>
#DELKEY $db Key1
#RETURN $db
</value>
<arglist>$db</arglist>
</func>
|
Then run this:
Code: |
TestList = 1|2|3|4
#SHOW %json(@TestList)
TestList2 = @_TestFunc1(@TestList)
#SHOW %json(@TestList) %json(@TestList2)
TestList3 = @_TestFunc2(@TestList)
#SHOW %json(@TestList) %json(@TestList3)
TestList4 = @_TestFunc3(@TestList)
#SHOW %json(@TestList) %json(@TestList4)
TestVar = "This added by alias."
#SHOW @TestVar
TestVar2 = @_TestFunc4(@TestVar)
#SHOW @TestVar "||" @TestVar2
#ADDKEY TestDB {Key1=1|Key2=2|Key3=3|Key4=4}
#SHOW %json(@TestDB)
TestDB2 = @_TestFunc5(@TestDB)
#SHOW %json(@TestDB) %json(@TestDB2)
|
To receive:
Code: |
[1,2,3,4]
[2,3,4] [2,3,4]
[2,3,4] [3,4]
[2,3,4,"Added by Function"] [2,3,4,"Added by Function"]
This added by alias.
This added by alias. || This added by alias. (This added by function.)
{"Key4":4,"Key3":3,"Key2":2,"Key1":1}
{"Key4":4,"Key3":3,"Key2":2} {"Key4":4,"Key3":3,"Key2":2}
|
You can see the two functions, _TestFunc1 & _TestFunc3, that use arglist to take a list argument modify the variable passed in, too.
Now, again I hesitate to say bug right off... while in the spirit of modularity any local function variables should impact solely within that scope unless explicitly declared otherwise (ala C pointer), but at the same time, this is probably also the easiest way to have that explicit declaration, since you can manage to maintain local scope using %XX if you want, and can use the arglist to have functions directly impact whatever is passed to them, allowing for more than one dynamic modification than you're limited to with #RETURN. It also saves having to try and figure out if somebody means a string or by-reference variable declaration in user functions. |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Mon Aug 02, 2010 4:17 pm |
The issue isn't actually with the arguments themselves or with user-defined functions. $arg and %1 actually call the same code. Neither of these make a copy when "reading" the argument. The issue is with the various %functions that accept a by-reference list value. Those functions (like %additem, %delitem, %delnitem, etc) treat local variable references such as $var as by-reference values. Since the named arguments are handled internally as local variables, then yes, using those in list functions will treat the list as by-reference and will change the passed list.
When using the %1 syntax to the %functions, the list is copied (because this doesn't use the local variable $var syntax).
I agree with what you are saying that "in theory" a function should not modify the list passed to it as an argument. But I don't want to change the fact that $var local variable references use by-reference. And without explicit declaration of arguments there is no way for the parser to tell the difference between $var used for local variables or for arguments...they both look the same. |
|
|
|
Zugg MASTER
Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Thu Aug 19, 2010 10:20 pm |
OK, so I think I came up with a good solution to this for the next release.
I've added a new function called %ref() that you can use to pass a variable "by reference". Then I fixed user-defined functions (and aliases) so that changing an argument doesn't change the calling list unless %ref was used.
Here are some examples:
Code: |
a = 123
b = @a
b = 999
#SHOW @a // still 123 as normal
b = %ref(a)
b = 999
#SHOW @a // now it is 999 because @a and @b point to the same value
a = 111
#SHOW @b // shows 111, again because @a and @b are the same pointer |
Code: |
#ALIAS Test($arg) {$arg = 999}
a = 123
Test @a
#SHOW @a // still 123
Test %ref(a)
#SHOW @a // now 999 because the alias changed it |
In the examples in the original post, it is all fixed so that the user functions do not modify the external list. However, if you change any of your calls to something like this:
Code: |
TestList2 = @_TestFunc1(%ref(TestList)) |
then the function *will* modify the external list
%ref will also work with local variables:
Code: |
$local1 = 123
$local2 = %ref($local1)
$local2 = 999
#SHOW $local1 // shows 999 since $local1 and $local2 point to the same value |
This new %ref function gives scripters a supported way to handle "by-reference" arguments instead of depending upon CMUD bugs/tricks. It also makes your scripts more obvious and supportable. If you don't use %ref() in a variable assignment or in an argument to a user alias/function, then you know that CMUD will make a safe copy of it. If you need the fastest performance and know what you are doing, then you can use %ref to prevent a copy from being made and pass the direct pointer to the variable instead.
The #VAR command will also print out more information about this. In the first example, after setting b=999, the #VAR command shows this:
Code: |
Variables:
a (Auto) 999 [ref(2) b ]
b (Auto) 999 [ref(2) a ] |
which shows that there are (2) references to the variable value and that @a and @b reference each other.
I'm not going to change any of the existing "by reference" stuff for list/table functions. Do doing:
Code: |
#CALL %additem(list,"123") |
is the same as doing:
Code: |
#CALL %additem(%ref(list),"123") |
|
|
|
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|