Does GPScript provide a way to produce multiple outputs?

Hi all,

Sorry for asking what may be obvious, I’m just not used to pass by reference type languages.

In one of my functions I do a lot of work that lead to three variables being updated at the end. From my MATLAB background, I am basically wired to define a multi-output function. But this seems not to be supported in GPScript (there is only a single result in all the examples I’ve found).

An alternative is of course to write the same function three times with different names and return only one of the variables each time. But this does three times the work at run-time and is not advisable for a myriad of reasons.

Do I understand correctly that one way to do this is as in the following function that explicitly returns a string but also updates one of its input arguments?

Function doSomething(a : Integer, b : Integer Array) returns String
var ExampleStrings  : String Array
    ExampleIntegers : Integer Array

ExampleStrings = ["a","b", "c", "d", "e", "f"]    
ExampleIntegers = [10, 20, 30, 40, 50, 60]    
result = ExampleStrings[a] // First output
b[a] = ExampleIntegers[a]  // Second output is an updated version of b

The function is called in a context such as this

var myString : String
    x : Integer Array

x = [10, 3, 5, 2, 7, 9]
myString = doSomething(3, x)

which would set myString = “c” and x = [10 , 3, 30, 2, 7, 9]

The integer array x that is passed to the function as argument b is not an input to the function (as I would read it with my background) but an output. It has no purpose as an input (although it could) but it is passed to the function only for the purpose of being updated.

Is this a correct way of viewing how GPScript handles arguments in user-functions?

I am not sur I understand your question, but I think your example shows that you understand how it works.
I think semantics is also important in the function naming to indicate how the parameters of the function will be processed.
Your example could also possibly be confusing for a user. When processing some datas, I am not sure the best idea is to mix the result of the processing like you are doing: half in the returned value, half by modifying values of the passed variables. But, perhaps that a good function naming would solve the problem: doSomethingToThePassedArrayAndReturnsAString.

Thanks @David-san, you’re too kind. That example was not a demonstration of understanding, but rather a hypothesis looking for confirmation.

After reading up on the different ways programming and scripting languages can handle their arguments on Wikipedia, all the potential ways this could potentially work, seemed dazzling and the attempt I made was merely an extrapolation of the examples I’ve come across so far.

But I’m happy to see that my interpretation was correct. I think I’ll get used to it :wink:

MATLAB is a very different animal - really designed for numerical and vector processing. Other than languages designed for vector processing, there aren’t many languages with built-in support for returning multiple values. Python is an example of one that does, using tuples.

Call by ref (seen mostly in the Algol/Pascal/Modula world) is basically an elegant wrapper around explicitly passing pointers around. C++ lets you use the & operator in a parameter call to do this.

Both call-by-ref semantics and returning tuples are on the list for GP Script at some point but no idea when that might happen, they’re both very low priority right now relative to other things we’re doing.

Using arrays (which are passed by reference meaning that if you change a value in an array parameter, that value is changed in the underlying array variable that was passed in) is certainly a way to work around the problem

1 Like

Thanks @dhj,
It helps a lot to have these conversations. Hopefully, it helps other people along too.

The basic risk of my ignorance of this mechanism is that I would not have thought about leaving the reference untouched in the body (scope) of the function. If it serves a purpose (e.g. avoiding an explicit copy action) I would surely have changed the input values, unwittingly changing the parent.

Good to have a firm grasp on it, now.

Hi @dhj and @David-san,
After some initial success (my first example was returning an array through the argument), I now run into erroneous behavior when using the same mechanism with an Integer. Could that be an implementation issue?
A simple example is given in this gig. All output is written to the Script Logger (there is no other functionality).
pass_output_to_argument_fails.gig (16.5 KB)

The script is added here:

    a          : Integer 
    b          : Integer Array 
    c          : Integer 
    d          : Integer 
Function check1(a : Integer, bee : Integer Array) returns Integer
// If a == 1, set b to 10 and return 5, otherwise set b to 8 and return 2
    If a == 1 Then
        bee[0] = 10
        result = 5
        bee[0] = 8
        result = 2

Function check2(a : Integer, dee : Integer) returns Integer
// If a == 1, set d to 10 and return 5, otherwise set d to 8 and return 2
    If a == 1 Then
        dee = 10
        result = 5
        dee = 8
        result = 2
    Print("Before returning, dee = " + dee)


    a   = 1
    b   = [0, 1, 2, 3, 4]
    c   = 0
    d   = 0

    Print("Function that returns a value and adjusts one of its arguments: Works with Array")
    Print("Before: a = " + IntToString(a) + ", b = " + IntToString(b[0]) + ", c = " + IntToString(c))
    c = check1(a, b)
    Print("After  : a = " + IntToString(a) + ", b = " + IntToString(b[0]) + ", c = " + IntToString(c))
    c = 0
    Print("Function that returns a value and adjusts one of its arguments: Doesn't works with Integer")
    Print("Before: a = " + IntToString(a) + ", d = " + IntToString(d) + ", c = " + IntToString(c))
    c = check2(a, d)
    Print("After  : a = " + IntToString(a) + ", d = " + IntToString(d) + ", c = " + IntToString(c))

The return in the Script Logger is

Rackspace: Function that returns a value and adjusts one of its arguments: Works with Array
Rackspace: Before: a = 1, b = 0, c = 0
Rackspace: After  : a = 1, b = 10, c = 5
Rackspace: Function that returns a value and adjusts one of its arguments: Doesn't works with Integer
Rackspace: Before: a = 1, d = 0, c = 0
Rackspace: Before returning, dee = 10
Rackspace: After  : a = 1, d = 0, c = 5

The output should be the same in both cases, but the return value gets updated and not the argument d, when that second argument is an Integer and not an array. As can be observed, the value dee inside the function just before returning is indeed 10 (or 8 if a != 1), but it is not passed back to the variable d in the top level code.

Not exactly.
From what I know, within a GP function, you work on a local copy of the variables passed as argument. This means that if you modify an Integer variable within a function, you only modify a copy of the originally passed variable which is therefore still unmodified when you return from the function call.
An array variable is an address container which points to the data it “contains” in fact only symbolically (i.e. the value of an array variable is an address in the computer memory). Within a GP function, as you work on a local copy of a variable passed as an argument, you will also work on a local copy of an array variable. Hence, if you could modify this local copy of the array variable, it wouldn’t affect the array variable originally passed as an argument. But, if you modify an element of the array, you access it through the address contained in the array variable, which means that you access the memory where the “real” data is stored and modify it there. When you return from the function call, the array variable you passed as a argument is still unmodified, but the data at the address it points have been modified.

I am not aware of what is implemented in GPScript, but it is probably close to what I tried to describe here.

That is not what I understand as “pass by reference”.
The name space is indeed local to the function. But if you pass the reference of a variable to a function, of which you modify the contents within that function, you do modify the only copy of the data which that variable represents. You can have a different local name of the reference (the variable’s address in memory) but there exists only a single value at that address, no copy. If you modify it, it has changed. Apparently, this is implemented in this way for Arrays, but not for Integers.

This is what @dhj wrote:

Yep, missed that nuance. Got it!

1 Like

And I don’t know if “passed by reference” is the correct wording here, as I suppose you cannot change the address contained in the array variable to make it point to other data. But, I am not sure anymore of the wording…

Passing an array by reference means that you can assign new values to entries in the passed array and you’re really updating the array that was passed in. In other words, you are referring to the original array.

GPScript does not (yet) have call by reference for base types such as integers but if it did, then you could write something like this

function foo ( ref i : integer)
   i = 42

   x : integer = 0

   foo (x)
   // x would now have the value 42

Passing an array by reference means you have access to the contents of the original array so you can change elements

Thanks for the clarification @dhj,
I had missed the nuance between the two because they now have the same syntax. When you do decide to add the pass by reference for base types, and have to add the ref in the argument, you may want to redefine how arrays are passed as reference, or add pass by name for arrays as well, such that the syntax can be harmonized.

Of course. The pass by reference for arrays was a deliberate choice to avoid having to copy lots of values all the time.