Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Unexpected Result (left to right paramater passing)


  • Please log in to reply
61 replies to this topic
Xander
  • Members
  • 140 posts
  • Last active: Nov 13 2007 08:31 PM
  • Joined: 23 Sep 2007

I think it would be more accurate to say that:[*:32gbx7ep]AHK fully evaluates each expression, pushing each result onto the stack,
[*:32gbx7ep]then assigns these results to the parameters (represented by variables) in right-to-left order (i.e. starting with the value most recently pushed onto the stack.)The issue is that an expression consisting solely of a variable name results in a reference to - not the value of - the variable. (Actually, the same applies to assignments, like index+=1.)

Yes, that is quite correct. A simple demonstration is as follows:
index = 1
MsgBox(index, index += 1, index++, ++index)
MsgBox(a, b, c, d) {
	MsgBox, % a " " b " " c " " d
}
Which gives 4 4 2 4
So in essence, it is storing the memory address of what is returned and not the value, and then assigning what is at each memory address after all expressions are evaluated.

Xander
  • Members
  • 140 posts
  • Last active: Nov 13 2007 08:31 PM
  • Joined: 23 Sep 2007

Why is relying on side effects in function parameters is bad style?
- It makes the code non-portable, hard to understand and document, difficult to optimize
- It could make the compiler/interpreter slower or larger.

It may require a little more work to port, but it doesn't render it entirely impossible. I'll have to just take your word for it that it is hard to understand a list of left to right expressions. And can you be more specific about how optimization is done currently that would have to be abandon if left to right evaluation was guaranteed?

If a piece of code does not rely on side effects, any parameter evaluation order gives the same result. Good programming style does not depend on parameter evaluation order.

As you may or may not be aware, global variables are also very bad programming, especially temporary variables! They can introduce practically unreproduceable bugs which are the most frustrating. That's one reason why I avoid temporary variables and ONLY use them as local variables in functions (this too can be problematic in AHK as variables are not limited by their scope or "block". To illustrate a problem with using temporary variables consider:
ATimerLabel:
	Loop, 100 {
		temp := 100 - A_Index
		func(temp)
	}
	return
F1::
	temp := func2()
	Send, %temp%
	return
If the F1 hotkey interrupts the Timer after it sets temp but before it sends it to func, then when ATimerLabel is resumed, temp will no longer be what it should be.

So as you can see, there is a very good reason to use expressions in the parameter list as opposed to assigning them to temporary variables and then passing them to the function.

Why is requesting a specific parameter evaluation order possibly too limiting?

- There are languages, where you can enter an expression as a parameter, and it will be newly evaluated each time it is used inside the function.

Now that would break a lot of scripts if it were changed to be like that !

I can see the use of such a feature, but it should be implemented in such a way that won't break code. Such as requireing an = sign before the parameter ( i.e func(=x++,=--y) ). So no, I am not buying your argument.

- Lazy evaluation could prevent run-time errors. Like this:

Select(x,sqrt(x),1/x,0)
Select(x,a,b,c) {
   Return x > 0 ? a : (x < 0 ? b : c)
}
It will not get in trouble with sqrt(-1) or 1/0.

Those could use the = syntax as well could they not. But I do agree, it could be very useful. But I don't know how complex it would be to implement and if that complexity would be warrented. Either way, fixed evaluation order does not limit this capability as you propose.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

index = 1
MsgBox(index, index += 1, index++, ++index)
MsgBox(a, b, c, d) {
	MsgBox, % a " " b " " c " " d
}
Which gives 4 4 2 4
So in essence, it is storing the memory address of what is returned and not the value, and then assigning what is at each memory address after all expressions are evaluated.

This result is rather surprising. Looks like in the case of index++, dereferencing of the variable is done, then increments the variable.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

[*:19irmpik]AHK fully evaluates each expression, pushing each result onto the stack,
[*:19irmpik]then assigns these results to the parameters (represented by variables) in right-to-left order (i.e. starting with the value most recently pushed onto the stack.)

Are you sure on this? Frankly, I'm doubtful on this. The stack is precious, and if the values which are of variable lengths are pushed on the stack, how could the called function know how much bytes should be popped/retrieved?
Although I can think of a way in AHK case, it's too troublesome and I don't think it's worth the effort.
And what about ByRef parameters?

ahklerner
  • Members
  • 1386 posts
  • Last active: Oct 08 2014 10:29 AM
  • Joined: 26 Jun 2006

....To illustrate a problem with using temporary variables consider:

ATimerLabel:
   Loop, 100 {
      temp := 100 - A_Index
      func(temp)
   }
   return
F1::
   temp := func2()
   Send, %temp%
   return

That code won't work on my machine :cry:

---------------------------
test.ahk
---------------------------
Error: Call to nonexistent function.

Specifically: func(temp)

Line#
003: Loop,100
003: {
004: temp := 100 - A_Index
---> 005: func(temp)
006: }
007: Return
008: Return
009: temp := func2()
010: Send,%temp%
011: Return
011: Exit

The program will exit.
---------------------------
OK
---------------------------


Posted Image
ʞɔпɟ əɥʇ ʇɐɥʍ

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

Looks like in the case of index++, dereferencing of the variable is done, then increments the variable.

Yes, that is necessary since post-increment returns the previous value of the variable. I think that every operation pushes its result onto the stack. However, any assignment operation (except post++) results in a reference to the variable itself.

If an assignment is used as the input for some other operator, its value is the variable itself. For example, the expression (Var+=2) > 50 is true if the newly-increased value in Var is greater than 50. This also allows an assignment to be passed ByRef, or its address taken; for example: &(x:="abc").

Are you sure on this?

Not entirely certain. I am certain that all parameters are popped off the stack and assigned to the function's variables (literally) all at once (by Line::ExpandExpression(), before the function is called.) For this to happen, they must have all been evaluated and pushed onto the stack beforehand.

Frankly, I'm doubtful on this. The stack is precious, and if the values which are of variable lengths are pushed on the stack, how could the called function know how much bytes should be popped/retrieved?

The called function doesn't use the stack - it uses (quite literally) variables which represent the parameters. Also, the stack is an array of (pointers to) tokens, so it doesn't matter how large each value is.

And what about ByRef parameters?

Tokens are a union of int64, double, string, and (pointer to) variable. ByRef parameters use said pointer to variable.


ahklerner, when did you add "Not a real bug" to your signature? :lol: (nice to know, btw)

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

ahklerner, when did you add "Not a real bug" to your signature? :lol: (nice to know, btw)

I tried to kill it with my thumb a couple of times…

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

any assignment operation (except post++) results in a reference to the variable itself.

This is the one which surprised me, not the operation of it.

The called function doesn't use the stack - it uses (quite literally) variables which represent the parameters.

Really? Are the variables of parameters assigned before the function call?
I mean, created, which is not a quite correct term with AHK, but I think you know what I mean.

Also, the stack is an array of (pointers to) tokens, so it doesn't matter how large each value is.

Now you said it. You should've said it from the start. Here is a quote from myself:

the stack should be filled with the parameters, pointers to the strings of actual parameters in AHK case, in reverse order,

ByRef parameters use said pointer to variable.

That was the point. If it's possible with ByRef parameters, why not use it, the pointer to (the variable of) the evaluated value, with other parameters? It's simple and elegant.

BTW, all my statements are done on the assumption that the parameters are created inside the function, not before the function call.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

BTW, all my statements are done on the assumption that the parameters are created inside the function, not before the function call.

I believe the parameter variables are created only once: when the function is defined (Script::DefineFunc().)

Really? Are the variables of parameters assigned before the function call?
I mean, created, which is not a quite correct term with AHK, but I think you know what I mean.

They are assigned values (func.mParam[j].var->Assign(token);) directly before the function is called (aResult = func.Call(result);). If the function has any instances already running, its variables are backed up beforehand (Var::BackupFunctionVars()) and reused.

If it's possible with ByRef parameters, why not use it, the pointer to (the variable of) the evaluated value, with other parameters? It's simple and elegant.

I'm not sure what you mean by that. The "evaluated value" doesn't have/isn't a variable unless the operation was an assignment (or there was no operation, as in func(var).) In these cases, the variable is pushed onto the stack regardless of whether it will be passed to a ByRef parameter (since - I guess - this wouldn't be known.) If the parameter isn't ByRef, the (result) variable's contents are assigned to the parameter.


There are probably inaccuracies in what I've posted, but I try not to state anything matter-of-factly until I'm confident it is fact. Line::ExpandExpression() is some ~2600 lines long, so I'm far from fully understanding it. :roll:

Btw, when I say "stack", I'm referring to a local variable of Line::ExpandExpression(), not the system's call stack.

Now you said it. You should've said it from the start.

I'm still learning (rereading the code) as I post. :)

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

I believe the parameter variables are created only once: when the function is defined (Script::DefineFunc().)

Yes, so I said:

I mean, created, which is not a quite correct term with AHK, but I think you know what I mean.

They are assigned values (func.mParam[j].var->Assign(token);) directly before the function is called (aResult = func.Call(result);). If the function has any instances already running, its variables are backed up beforehand (Var::BackupFunctionVars()) and reused.

It's a surprise for me, nevertheless, seems feasible.

If it's possible with ByRef parameters, why not use it, the pointer to (the variable of) the evaluated value, with other parameters? It's simple and elegant.

I'm not sure what you mean by that.

What I thought was the following:
1) pre-evaluating the expressions of parameters. Then, finally allocate and fill the buffers for the parameters in reverse-order. Dereferencing of the variables for ByVal parameters are done at this stage.
2) After each step of allocating and filling the buffer, push the pointer of the buffer onto the stack. If the parameter is of ByRef, then use the pointer of the variable. After finishing them, call the function. Then, the function pops the pointers off the stack and assigns the parameter-variables to correspond to the pointers. At this assigning level, no need for the function to differentiate between ByRef and ByVal. They are on an equal footing.

According what you said, however, AHK skips the second step, directly assigns the parameter-variables in the first step.

Line::ExpandExpression() is some ~2600 lines long,

Wow, good luck then. ;)

Xander (not logged in)
  • Guests
  • Last active:
  • Joined: --

....To illustrate a problem with using temporary variables consider:

ATimerLabel:
   Loop, 100 {
      temp := 100 - A_Index
      func(temp)
   }
   return
F1::
   temp := func2()
   Send, %temp%
   return

That code won't work on my machine :cry:

Sorry, it wasn't meant to be a functional script!

You would have to give definitions for func(param) and func2() as well as use SetTimer to have ATimerLabel run every so often.

Xander
  • Members
  • 140 posts
  • Last active: Nov 13 2007 08:31 PM
  • Joined: 23 Sep 2007

any assignment operation (except post++) results in a reference to the variable itself.

This is the one which surprised me, not the operation of it.

post-fix ++ by itself is not considered an assignment because their is no logical variable to assign it to (except perhaps an imaginary one).

That is why this is not allowed:
index = 1
MsgBox(index, index++)

MsgBox(a, ByRef b) {
	MsgBox, % a " " b
}

But this is:
index = 1
MsgBox(index, ++index)

MsgBox(a, ByRef b) {
	MsgBox, % a " " b
}


Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

post-fix ++ by itself is not considered an assignment

Where is it stated in the documentation? If it's not in the documentation, this statement is mere tautological transformation, inaccurate IMO as it ignores the incrementation of the variable itself, of my statement. I had immediately tested the following after my first post on this:
i:=0
Test(++i)
Test(i++)

Test(ByRef i){
	MsgBox, %i%
}

because their is no logical variable to assign it to (except perhaps an imaginary one).

I don't know what you're talking about. Please clarify on this: an assignment is occurred or not?

Xander
  • Members
  • 140 posts
  • Last active: Nov 13 2007 08:31 PM
  • Joined: 23 Sep 2007

Where is it stated in the documentation? If it's not in the documentation, this statement is mere tautological transformation, inaccurate IMO as it ignores the incrementation of the variable itself, of my statement.

I am not sure what you are looking for here. Anyway the documentation has this to say about ++

If it appears before the name, the operation is performed immediately and its result is used by the next operation. For example, Var := ++X increments X immediately and then assigns its value to Var. Conversely, if the operator appears after the variable's name, the operation is performed after the variable is used by the next operation. For example, Var := X++ increments X only after assigning the current value of X to Var.

Which of course is the normal behaviour of ++ in C++ and Java (and probably many other languages as well).

I don't know what you're talking about. Please clarify on this: an assignment is occurred or not?

Yes an assignment occurs, but the variable x is not returned, the value of x is returned.

As you probably are aware, x++ becomes:

return x
x += 1

But if there is nothing to return x to, for example if you just have this as a statement:

x++

the return portion is essential thrown out and it mearly becomes

x += 1

Similarly ++x becomes:

x += 1
return x

And if you just have ++x on a single line, it is no different from x++ as there is nothing to return x to after it is incremented.

So when you have ++x as an expression in a parameter list, the variable x is incremented and then returned, whereas if you have x++ as an expression in a parameter list, the value of x is return and then x is incremented.

I am not sure I follow why you think the variable (or address of) x should be returned when x++ is used, can you please explain? I think I've explained my reasoning about as best as I can unless I know more about what you are thinking.



Also, allow me to clarify that I think the value should be returned regardless of whether ++x or x++ or x += something is used. Though I see why it isn't in the current 2 stage implementation because it is not known if the parameter will be ByRef or not. This probably could be allieviated by using a single step, that is, do the evaluation and determine if the parameter is ByRef before continueing to the next parameter evaluation.

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

And if you just have ++x on a single line, it is no different from x++ as there is nothing to return x to after it is incremented.

I'm impressed. You really explained thoroughly.
BTW, this is what made me think x++ would behave differently when it's the sole operation.
After the post, I felt some deja vu. I think I already tested it once, but completely forgot about it.
So, I seem to have a difficulty to accept this behavior.

Also, allow me to clarify that I think the value should be returned regardless of whether ++x or x++ or x += something is used.

Personally I agree with you on this, but it may be not so simple to change.
I'm regarding it as the cost I have to pay for to enjoy the great flexibility of AHK.