0

EDIT: I realize many of the things I am running into are artifacts of the updated V8 GAS runtime. The question is updated now to reflect these changes.

I am encountering some interesting behavior in Google Apps Script. I have written a small demonstration function which demonstrates this behavior.

The function below is a wrapper function which allows me to test the "phaseToSequenceIssueDemonstration" function.

function testPhaseToSequence(){
  var phaseSequenceArray = phaseToSequenceIssueDemonstration([[26.1877,21.333],[17.828,-102.813],[10.812,139.288]]);
  Logger.log("PHASE SEQUENCE ARRAY:" + phaseSequenceArray);
}

This is the "phaseToSequenceIssueDemonstration" function which the wrapper function above is calling:

function phaseToSequenceIssueDemonstration(phaseArray){
  Const polarArray = phaseArray.splice();
  Logger.log("Polar array before function call: " + polarArray);
  Logger.log("Phase array before function call: " + phaseArray);
  var cartArray = arrayPolarToCart(polarArray);
  Logger.log("Polar array after function call: " + polarArray);
  Logger.log("Phase array after function call: " + phaseArray);
}

As you can see, I'm assigning polarArray to the variable phaseArray, which is passed in the wrapper function. I then push to the logger the values of both variables, which should be the same.

I am then creating another variable "cartArray," passing polar Array into this function, and storing the result.

The arrayPolarToCart function is seen below. It takes an array, performs a manipulation, and returns it in it's new form:

function arrayPolarToCart(array){
  //Contains the continuous subtraction of all vectors in the array.
    const total = array.slice();
    let cartesianArray = total.slice();
  for(var i = 0; i<array.length; i++){
    var currentVector = total[i];
    var currentMag = total[i][0];
    //Logger.log(currentMag);
    var currentAngle = total[i][1];
    // Logger.log(currentAngle);
    var currentX = currentMag*Math.cos(Math.PI*(currentAngle/180));
    var currentY = currentMag*Math.sin(Math.PI*(currentAngle/180));
    cartesianArray[i][0] = currentX;
    cartesianArray[i][1] = currentY;
  }
    //Logger.log(total);
    return cartesianArray;
}

Notice that there is no further change or assignment of either the phaseArray or polarArray variables after the first line of the function.

Here's what the loggers seen in the function print: enter image description here

Notice that both the variable passed into this function, and the variable assigned at the beginning have changed after the assignment of the cartArray variable, despite there never being an assignment statement in the function to change them and assigning const and splice where appropriate to create new references.

How could this be?

Marios
  • 26,333
  • 8
  • 32
  • 52

2 Answers2

1

Using = doesn't actually create a deep clone of the array, so total and phaseArray are basically the same. You can use .slice() or the spread syntax to create a deep clone.

// Shallow clone
var a = [1, 2, 3];
var b = a;

b.forEach(function(val, i) {
  b[i] = val*2;
});
console.log(a); // [2, 4, 6]
console.log(b); // [2, 4, 6]

// Deep clone
var a = [1, 2, 3];
var b = a.slice();

b.forEach(function(val, i) {
  b[i] = val*2
});
console.log(a); // [1, 2, 3]
console.log(b); // [2, 4, 6]

Alternatively, or even in combination with, you should use higher-order functions which will help you prevent issues like this. In the example below, I've rewritten arrayCartToPolar() to use the map method. You can also consider using const for variable declaration which will forcibly prevent any changes.

phaseToSequenceIssueDemonstration([[26.1877,21.333],[17.828,-102.813],[10.812,139.288]]);

function phaseToSequenceIssueDemonstration(phaseArray) {
  var polarArray = phaseArray.slice();
  console.log("Polar array before function call: " + polarArray);
  console.log("Phase array before function call: " + phaseArray);
  arrayCartToPolar(polarArray);
  console.log("Polar array after function call: " + polarArray);
  console.log("Phase array after function call: " + phaseArray);
}

function arrayCartToPolar(array) {
  return array.map(function(val) {
    var currentX = val[0];
    var currentY = val[1];
    var currentMag = Math.sqrt(currentX*currentX + currentY*currentY);
    var currentAngle = (Math.atan2(currentY, currentX)/Math.PI)*180;
    return [ currentMag, currentAngle ];
  });
}

Suggested Reading:

Diego
  • 9,261
  • 2
  • 19
  • 33
  • Some useful points here. I am so familiar with the old Rhino runtime (ES5) that GAS previously run on that all the new syntax typical of more modern versions of ECMAScript is foreign to me. Nonetheless, I have used const to define my polarArray variable and also splice(); when assigning the phaseArray variable to it. In order to both create a 'deep clone' and to prevent changes from occurring to the polarArray variable. And still, after running the arrayPolarToCart function and passing the polarArray variable, I get the exact same print logs. Any ideas? Question is updated with changes. – Jaden Ranzenberger Jan 16 '21 at 19:49
  • @JadenRanzenberger I've updated my code to make it Rhino compatible so it's easier for you to understand. Use the `.map()` method. – Diego Jan 16 '21 at 21:10
1

I can see four issues in your code, but you are interested in the last two:

  • In testPhaseToSequence you assign phaseToSequenceIssueDemonstration to a variable but phaseToSequenceIssueDemonstration does not return anything.

  • In the function phaseToSequenceIssueDemonstration(phaseArray) you call the function arrayPolarToCart which you haven't defined anywhere. However, you have defined arrayCartToPolar, so please fix that.

  • Last but not least and this should be the answer of your question. In phaseToSequenceIssueDemonstration you pass polarArray to arrayCartToPolar. As you can see this function modifies the array and hence you get a different result after the call of that function. How? You pass a reference of polarArray to arrayCartToPolar and then you pass this reference to total. Remember, now array and total are referencing the exact same object which is polarArray. That means that whatever change you do on total or array it will be reflected to polarArray and that is the reason you are getting a different output after the call of arrayCartToPolar.

  • Based on the same logic, phaseArray passes its reference to polarArray. So whatever change you do to polarArray is instantly reflected to phaseArray. This is why polarArray and phaseArray return the same value. Simply because they refer to the same object.

This is not a Google Apps Script behaviour, but a JavaScript behaviour and it is actual a common behaviour of many other programming languages. So I would advice you to get familiar with scopes and definitions. Here is a good resource for that.

Marios
  • 26,333
  • 8
  • 32
  • 52
  • Very helpful- I realize most of this stuff is foreign because I am used to ES5 which was supported previous to the new V8 runtime. I have updated my question given the information you provided here. I still am running into the same issue, however. – Jaden Ranzenberger Jan 16 '21 at 19:58