PSA: understanding scope in array functions

From the docs:

  • Closures aren’t supported, so any function defined in another function won’t have access to parameters or local variables. It can still access globals or its own parameters.

This is a little different from normal JS, and it most commonly catches me when using the array functions.

Array functions can access globals. Say we wanted to normalize all elements of an array by finding the max value and dividing all elements by that max.

var maxVal
function normalize(arr) {
  maxVal = arr.reduce((acc, v) => max(acc, v), arr[0])
  arr.mutate(v => v / maxVal)
}

Docs again:

Explicitly declared variables will either be global or local depending on where they are declared. Local variables declared using var inside a function are visible inside that function.

The following works the same, because without var preceding maxVal inside the function, it defines maxVal as a global:

// var maxVal // Notice the only change is that this line is now commented out. Works the same!
function normalize(arr) {
  maxVal = arr.reduce((acc, v) => max(acc, v), arr[0])
  arr.mutate(v => v / maxVal)
}

But back to this again:

any function defined in another function won’t have access to parameters or local variables

Array functions commonly pass functions defined using arrow function notation (=>), and again: functions within functions cannot access locals. This code can’t see maxVal because the var defining it inside normalize() means it’s a local variable.

// Won't work
function normalize(arr) {
  var maxVal = arr.reduce((acc, v) => max(acc, v), arr[0])
  arr.mutate(v => v / maxVal) // Undefined symbol maxVal
}

I hope I stumble on my own post here in 3 months when I forget why my pithy lambdas couldn’t see a variable :woozy_face:

4 Likes

Thanks, @jeff ! Good writeup / expansion on how these work.
FWIW, I do hope to improve on these at some point. There’s a few tricks I can use if I can prove through static analysis that a function is never stored or passed outside of a scope to be able to access other stack variables like parameters or local vars. Outside of that, I think closures are often implemented as objects that would need to be GCed at some point, which would go along with other object support.

So e.g.

function normalize(arr) {
  var maxVal = arr.reduce((acc, v) => max(acc, v), arr[0])
  arr.mutate(v => v / maxVal) // Undefined symbol maxVal
}

Would be possible (and fast) with some extra compiler smarts.

1 Like