Do we need to return multiple variables?

Discuss the future of the AutoHotkey language
arcticir
Posts: 694
Joined: 17 Nov 2013, 11:32

Do we need to return multiple variables?

Post by arcticir » 23 Apr 2020, 07:56

Many modern languages ​​support this feature, and I find it very convenient and necessary.
Do you think AHK needs this feature?

x,y := MouseGetPos()
color := PixelGetColor(MouseGetPos(),"Alt")
var, err := iniread("test.ini","default","test")
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Do we need to return multiple variables?

Post by Helgef » 23 Apr 2020, 09:11

Convenient, in some cases, sure. Necessary, certainly not. Functions such as mousegetpos could return an object instead, it would make more sense in your example imo.

Cheers.
guest3456
Posts: 3458
Joined: 09 Oct 2013, 10:31

Re: Do we need to return multiple variables?

Post by guest3456 » 23 Apr 2020, 09:55

for line 2 to work, it'd have to return an array to populate the func params ala a varaiadic func, right?

User avatar
Delta Pythagorean
Posts: 627
Joined: 13 Feb 2017, 13:44
Location: Somewhere in the US
Contact:

Re: Do we need to return multiple variables?

Post by Delta Pythagorean » 24 Apr 2020, 04:34

As far as I know, no. You can span the returned object like you would spread an array, although I think it sorts it so it would be unreliable in various cases.
As for assigning a value to multiple variables, you can do it:

Code: Select all

MouseStart := ExampleObject := MouseGetPos()
This would set both variables to the same values all on the same line. Although it would be nice to have this feature, even in v1, it seems a little impractical given you can do the above method.

[AHK]......: v2.0.12 | 64-bit
[OS].......: Windows 11 | 23H2 (OS Build: 22621.3296)
[GITHUB]...: github.com/DelPyth
[PAYPAL]...: paypal.me/DelPyth
[DISCORD]..: tophatcat

arcticir
Posts: 694
Joined: 17 Nov 2013, 11:32

Re: Do we need to return multiple variables?

Post by arcticir » 24 Apr 2020, 07:39

@Helgef When I get used to this feature in other languages, I think it is very important.
You deny its necessity, have you really used it? If you don't really use it, you can't understand its importance.
Just like ahk's support for objects, even if there is no object, ahk can also achieve the function of all objects. But when you are used to using objects, do you think objects are not necessary?


@guest3456 I think it will be more complicated at the bottom of C.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Do we need to return multiple variables?

Post by Helgef » 24 Apr 2020, 09:03

Objects can do similar to what you suggest, and more, so no, it's not necessary. No-one need to have used it to judge its necessity in ahk, although I have used it, and I don't think it is necessary for ahk.

Anyways, I think you should outline your suggestion in more detail. There many things to consider.
_3D_
Posts: 277
Joined: 29 Jan 2014, 14:40

Re: Do we need to return multiple variables?

Post by _3D_ » 25 Apr 2020, 03:37

The short answer is NO.
AHKv2.0 alpha forever.
lexikos
Posts: 9569
Joined: 30 Sep 2013, 04:07
Contact:

Re: Do we need to return multiple variables?

Post by lexikos » 22 May 2020, 23:41

There is a problem with the suggested syntax: it is not a natural extension of the existing assignment and comma operators. Currently the comma has lower precedence than assignment, meaning the grouping is like (x), (y := MouseGetPos()).

I suppose it would need to be a special case for when an expression statement begins with variable, variable ... :=. Single-target assignments can be performed anywhere within an expression, but that couldn't be done without ambiguity for multiple targets. Lua allows multiple return values, but does not allow assignments within an expression (it uses = for both assignment and comparison). That's probably how it is in most languages with multiple return values.

There is another way to handle it. JavaScript traditionally only supports single-return, and unlike AutoHotkey, has no ByRef. So to return multiple values, one must return an object. Objects are used so heavily in JavaScript that recent versions of JavaScript (or ECMAScript) have added destructuring assignments. For example,

Code: Select all

var [x, y] = MouseGetPos(); // if it returns an array
var color = PixelGetColor(...MouseGetPos(),"Alt");
var {x, y} = MouseGetPos(); // if it returns an object with x and y properties
var color = PixelGetColor(x, y, "Alt"); // must pass them explicitly
// (but it's probably more conventional in JavaScript to pass an object).
How would you get just the second, third or fourth value? As it is now, you can omit the first two or three parameters if you just want the window or control HWND. In JavaScript...

Code: Select all

var [,, hwnd] = MouseGetPos();
The "rest" operator is used to get the rest of the values (and the same syntax is used for variadic functions):

Code: Select all

var [x, y, ...the_rest] = MouseGetPos();
AutoHotkey doesn't have a general var declaration keyword, and declaring variables with global has potentially unwanted side effects. A statement beginning with [...] := would not be ambiguous, but something like {...} := is trickier because of the way blocks are handled. Destructuring assignments can be quite complex, since they can handle nested objects and arrays.

If it is possible to work destructuring assignments into the language, the same syntax could (instead) be used to deal with multiple return values. But in the end it's somewhat redundant, since we already have objects, arrays and ByRef parameters.


Also consider that depending on the language and context, sometimes a function call in a parameter list automatically results in multiple parameter values, and sometimes only the first value is used. In Lua only the first value is used, unless the function call is at the end of the parameter list.

Looking at the example PixelGetColor(MouseGetPos(),"Alt"), it's hard to see that three parameters are being passed. The meaning of subsequent parameters depends on how many values MouseGetPos() returns. If it's a user-defined function, there might be multiple return statements and each one could return a different number of values. Any function could do this, so any use of a function call directly in a parameter list becomes suspect.

On the other hand, if the function returns an array, PixelGetColor(MouseGetPos()*,"Alt") at least shows that the number of parameters might vary based on the number of values returned.

Of course, I'm only now realizing a major problem with both the idea and example: invalid assumptions would be made about the number of values a function returns. MouseGetPos has four return values, so it would not be safe to use directly as a set of coordinates (X, Y).
lexikos
Posts: 9569
Joined: 30 Sep 2013, 04:07
Contact:

Re: Do we need to return multiple variables?

Post by lexikos » 22 May 2020, 23:45

guest3456 wrote:
23 Apr 2020, 09:55
for line 2 to work, it'd have to return an array to populate the func params ala a varaiadic func, right?
This works in v1:

Code: Select all

Loop {
    Sleep 50
    ToolTip % PixelGetColor(MouseGetPos()*)
}

MouseGetPos() {
    MouseGetPos x, y
    return {x:x,y:y}
}
PixelGetColor(X, Y, Mode:="") {
    PixelGetColor C, X, Y, % Mode
    return C
}
However, this obscure capability was lost when objects were restructured. In v1 it had the following caveats:
  • You must pass an Object (a ComObjArray, for instance, will not work).
  • Sequential parameters must exist as key-value pairs, with the relative position being the key.
  • Named parameters must exist as key-value pairs.
  • The target of the call must be a user-defined function, not a method call or built-in.
In v2, variadic calls accept any enumerable object. The first value returned in each iteration of the enumerator takes one position in the parameter list. You can pass any kind of object which has an __Enum method or a Call method which accepts a ByRef parameter. In other words, you can use [anyEnumerableObject*] to create an Array of the enumerated values. For example, you can use this to convert a ComObjArray to an Array.

To permit passing an object like {x:x,y:y} to the built-in PixelGetColor function, there are a couple of options:
  • Define parameter names for PixelGetColor in a way the program can utilize, and restore the capability to specify named parameters via an object's properties.
  • Define a sequential order for the object's properties, either by convention or within the script.
Without this, the program (and perhaps script author) cannot determine which parameter position should be filled with which property.
iseahound
Posts: 1441
Joined: 13 Aug 2016, 21:04
Contact:

Re: Do we need to return multiple variables?

Post by iseahound » 23 May 2020, 09:32

One well reasoned argument for multiple return values would be for clarity of type signatures. Any benefits over returning an object would be tied to type declarations of return values.

Would a simple (a, b) := MouseGetPos() be possible?

Here's an example from Go:

Code: Select all

func vals() (int, int) {
    return 3, 7
}
https://gobyexample.com/multiple-return-values

In other words, it would allow for enhanced runtime error checking, a benefit that the current ByRef syntax does not provide.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Do we need to return multiple variables?

Post by kczx3 » 23 May 2020, 18:54

Destructuring assignments would be fantastic!
lexikos
Posts: 9569
Joined: 30 Sep 2013, 04:07
Contact:

Re: Do we need to return multiple variables?

Post by lexikos » 23 May 2020, 19:29

lexikos wrote:
22 May 2020, 23:45
However, this obscure capability was lost when objects were restructured.
Scratch that; there is still support for passing named parameters as properties:
When a user-defined function is called directly, the object may supply parameter values via properties named the same as the function's parameters. However, positional parameters take precedence. Named parameters are not supported when calling methods or built-in functions.
This is much the same as before, but now it conflicts with the non-Array behaviour:
If the object is not an Array, __Enum is called with a count of 1 and the enumerator is called with only one parameter at a time.
Where before you had to pass an Object, because the array portion of it was copied directly, now you have two options:
  1. Pass an Array, in which case the array portion is copied directly.
  2. Pass any other object which is enumerable, and its enumerated values will be used.
In both cases, a named property is queried for each parameter which has not otherwise been supplied a value. However, this doesn't happen if the object is not enumerable, as an exception has been thrown.

I was thinking I had removed this limited support for named parameters because I was misremembering this:
The target function may also be variadic, in which case properties which do not correspond to formally defined parameters are discarded.
What it's trying to say is that unused (or used) properties from the caller's object are not copied to the callee's object. In v1, all non-integer key-value pairs are copied from the caller's object to the callee's object; to keep this low cost, it's done with a single function call which includes key-value pairs that have already been assigned to parameters. Between writing new code to handle the new structure and just removing this niche feature for now, I chose the latter.


Anyway, this works in v2-a108:

Code: Select all

Loop {
    Sleep 50
    ToolTip myPixelGetColor(myMouseGetPos()*)
}

myMouseGetPos() {
    MouseGetPos x, y
    a := [], a.x := x, a.y := y
    return a
}
myPixelGetColor(X, Y, Mode:="") {
    return PixelGetColor(X, Y, Mode)
}
It doesn't have to be an Array; it could be an object with no enumerated values (no positional parameters), such as one with a call method defined as Call(ByRef v) => false.


iseahound wrote:In other words, it would allow for enhanced runtime error checking, a benefit that the current ByRef syntax does not provide.
Multiple return values would, or may, permit you to:
  • Specify the variable before the assignment instead of as a parameter.
  • Discard any or all return values regardless of the function author's intention.
  • Pass the function's return values directly to another function as multiple parameters. (There may be restrictions, as in Lua.)
Aside from that, the difference to ByRef syntax is basically aesthetic. ByRef may not provide "enhanced runtime error checking", but neither would multiple return values.

Also, this is not Go. Any benefits that multiple return values would have in conjunction with type declarations are non-existent, because type declarations are not under consideration. Type declarations in AutoHotkey are a pipe dream more likely fulfilled by switching to another language.
Last edited by lexikos on 24 May 2020, 00:08, edited 1 time in total.
Reason: Correction; named parameters work with non-Array as long as the object is enumerable.
iseahound
Posts: 1441
Joined: 13 Aug 2016, 21:04
Contact:

Re: Do we need to return multiple variables?

Post by iseahound » 13 Jun 2020, 13:28

I've been playing around with v2 for a bit, and I've noticed that the fat arrow functions use commas as line separators. Currently the last value after the last comma is implicitly returned (without a return statement). If multiple return values are implemented, this functionality would break. It's currently very useful to have multi-line statements in function definitions that don't return, like Javascript, but it's also useful to have multiple return values.
User avatar
kczx3
Posts: 1640
Joined: 06 Oct 2015, 21:39

Re: Do we need to return multiple variables?

Post by kczx3 » 13 Jun 2020, 20:22

There’s nothing special about commas and fat arrow functions. Commas simply act as continuation characters. It may be used to sort of achieve a function body like in JavaScript. However, the current implementation of fat arrow functions in AHK aren’t really meant to be multi lined.
lexikos
Posts: 9569
Joined: 30 Sep 2013, 04:07
Contact:

Re: Do we need to return multiple variables?

Post by lexikos » 13 Jun 2020, 23:29

If they aren't separated by a line ending sequence, they aren't lines. The comma operator separates sub-expressions.

Also, it has very little to do with fat arrow functions.

Code: Select all

fat_arrow_function() => 1, 2  ; Error: "Return" accepts at most 1 parameter.

Code: Select all

MsgBox (() => 1, 2)  ; Shows 2 (the fat arrow function ends at the comma).
I assume you're just using the ordinary comma operator in its ordinary capacity within an expression.

Code: Select all

MsgBox (1, 2)  ; (1, 2) is an expression that returns 2 (but not in v1).
f := a => (1, 2)  ; This fat arrow uses the one expression.
return (1, 2) should always produce one value, because the sub-expression (1, 2) always produces one value. If you write f((1, 2)), for instance, you don't get two parameters.
Post Reply

Return to “AutoHotkey Development”