Jump to content


for k, v in x -- odd behavior in recursion


  • Please log in to reply
8 replies to this topic

#1 chlawson

chlawson
  • Guests

Posted 03 July 2012 - 06:01 AM

In version 1.1.07.03, I'm having trouble doing byref recursion in a function that employs "for k,v in x" iterator -- when in fact "for k in x" (without the ,v) works fine. I know the code below doesn't really do anything of substance and is not a good/well-formed use case. My real function is pretty involved, while this quick example demonstrates the core of the issue I've encountered. In much simplified form, I'm trying to figure out why this code works:

r(byref a) {
	if !isobject(a)
		msgbox % a
	else
		for k in a
			r(a[k])
}
x := [10,20,30]
r(x)

but this code doesn't (using k,v rather than a[k]):

r(byref a) {
	if !isobject(a)
		msgbox % a
	else
		for k, v in a
			r(v)
}
x := [10,20,30]
r(x)

Also, this does not work either (using intermediate variable x for the value v):

r(byref a) {
	if !isobject(a)
		msgbox % a
	else
		for k, v in a
			x := v, r(x)
}
x := [10,20,30]
r(x)

Is this performing as you had intended? As I said, this is a very simplified version of an issue I'm having with a more complex function. I realize that if I don't pass byref, that also cures the problem. However, I need byref for the more complex use case. I just couldn't visualize why it's not working right -- especially since the byref variable "a" is never modified.

Thanks for your help -- Lexikos, you are the best!

#2 HotKeyIt

HotKeyIt
  • Fellows
  • 6191 posts

Posted 03 July 2012 - 09:25 AM

Try this:
r(ByRef a) {

  static var

   if !isobject(a)

      msgbox % a

   else

      for k, v in a

         r(var:=v)

}

x := [10,20,30,[100,200,300]]

r(x)


#3 chlawson

chlawson
  • Guests

Posted 05 July 2012 - 12:05 AM

Thanks for the reply, HotKeyIt. Your static var approach works, and so does the first code block in my OP using r(a[k]) rather than r(v) with respect to the "for k,v in a" iterator. What I'm not understanding for sure is if I can call r(a[k]) using recursion why I can't call r(v). Now I've got two great workarounds, but I'm still struggling to understand the base behavior of the iterator. To illustrate my question, here's a modified form of my OP, which uses a[k] and does work:

r(byref a) {
	if !isobject(a)
		msgbox % a
	else
		for k,v in a {
			var_ak := a[k],  var_v := v
			msgbox % "k:`t|" &k "|`nv:`t|" &v "|`nvar_ak:`t|" &var_ak "|`nvar_v:`t|" &var_v "|`na[k]:`t|" &(a[k]) "|" 
			r(a[k])
		}
}
x := [10,20,30]
r(x)

It appears I can't print a memory address of a[k] directly, so I'm using var_ak as a proxy. I'm surprised that v and var_ak wouldn't be pointing to the same address. I'm wondering if this has something to do with why r(a[k]) works and r(v) doesn't. I would have thought a[k] and v in the "k,v in a" construction would have pointed to the same place... no? I think I might just be being dense, but if someone could explain it to me, I'd really appreciate it.

No rush though. I do have a workaround (or two, thanks to you!) and my actual app is working fine. Thanks for your help!

#4 Lexikos

Lexikos
  • Administrators
  • 8922 posts

Posted 05 July 2012 - 05:49 AM

Please note:

Although a function may call itself recursively, if it passes one of its own local variables or non-ByRef parameters to itself ByRef, the new layer's ByRef parameter will refer to its own local variable of that name rather than the previous layer's. However, this issue does not occur when a function passes to itself a global variable, static variable, or ByRef parameter.
Source: Functions - ByRef

Also note you do not need to use ByRef to pass an object reference. ByRef should only be used if you need to assign another value or object to the variable. It means "pass variable by reference".

#5 Lexikos

Lexikos
  • Administrators
  • 8922 posts

Posted 05 July 2012 - 08:23 AM

if I can call r(a[k]) using recursion why I can't call r(v).

ByRef indicates that the function accepts a reference to a variable. a[k] is not a "variable" in the strict context of the documentation or implementation, and therefore not passed by reference.

[AHK_L 60+]: If something other than a modifiable variable is passed to a ByRef parameter, the function behaves as though the keyword "ByRef" is absent. For example, Swap(A_Index, i) stores the value of A_Index in i, but the value assigned to Left is discarded once the Swap function returns.
Source: Functions

Internally, a[k] is essentially an element in an array of key-value pairs, where any element could be moved or the entire array could be freed/reallocated at any time. The only safe way to refer to it is to have a reference to the object and a copy of the key (or keys: a[x, y] is also valid). The current "variable reference" or "ByRef" mechanism simply isn't capable of this.

It appears I can't print a memory address of a[k] directly,

The &address-of operator has two functions:


[*:t66xbgb5]Retrieve the address of a variable's string buffer (see below).
[*:t66xbgb5]Retrieve the address of an object.
In the first case, only a plain "variable" reference is accepted. Since a[k] is not a "variable" and does not contain an object, &a[k] will not work. If it contained a string, you could use a.GetAddress(k), but in this case it contains an integer. Like with the element itself (see above), the actual location in memory of this integer can change as other items are added or removed from the object, so retrieving its address is not supported.

I'm surprised that v and var_ak wouldn't be pointing to the same address.

Each variable has its own buffer for storing strings, and therefore an address which is unique to that variable (but can change). VarSetCapacity sets or retrieves the capacity of this buffer.

#6 chlawson

chlawson
  • Guests

Posted 05 July 2012 - 09:34 PM

Lexikos, thanks for the thorough explanation. That clears a lot of things up in my mind that were not crystal clear before. So I have only one remaining question regarding your quote from the docs (which does make sense to me):

If something other than a modifiable variable is passed to a ByRef parameter, the function behaves as though the keyword "ByRef" is absent.


Why then does the second snippet of code in my OP not work? Namely:

r(byref a) {
   if !isobject(a)
      msgbox % a
   else
      for k, v in a
         r(v)    ; it works with r(a[k]) in place of r(v)
}
x := [10,20,30]
r(x)

If the only downside of a non-variable passed by reference is that any changes to it are discarded, shouldn't this still work with r(v) if there's nothing being changed? It appears that when calling r(v), the "byref a" variable receives nothing, which accounts for the blank message box. But if we either (1) use a[k] instead, or (2) remove byref, it works fine.

Is this the behavior you had intended? If so, can you tell me why "v passing byref to a" loses its value in the function call? Is something changing the value of a or v that I'm not seeing? Is it something peculiar to the "for k,v in a" construction?

Thanks again for all your help!

#7 HotKeyIt

HotKeyIt
  • Fellows
  • 6191 posts

Posted 05 July 2012 - 10:36 PM

Please note:

Although a function may call itself recursively, if it passes one of its own local variables or non-ByRef parameters to itself ByRef, the new layer's ByRef parameter will refer to its own local variable of that name rather than the previous layer's.

So the function you call recrusively has the variable v now in ByRef a, but the v is its local v because previous layer's v has been backed up and cleared due to function call (because it is a local var).

#8 infogulch

infogulch
  • Moderators
  • 717 posts

Posted 05 July 2012 - 11:47 PM

Since we're using ahk_l, may I suggest passing an object for recursive calls (NOT as ByRef)?

Changes made to the object in deeper recusion levels will still apply to the object in the local var in other recursion levels.

#9 chlawson

chlawson
  • Guests

Posted 06 July 2012 - 12:18 AM

Got it -- it's the current layer's empty variable v that is in the byref a variable. Thanks to everyone for the clarifications.