Back in 2014, I wrote a blog post exploring the different types of variables in FileMaker and their scopes:
- Global ($$myVar – at the file level for the duration of the user’s session )
- Local ($myVar – at the script level for the user’s execution of the script)
- Calculation( _myVar – at the calculation level, for as long as it takes to complete the calculation. Unlike Global and Local variables, they don’t have a mandatory prefix. I tend to use an underscore to make sure they do not get confused with other function names or field names.
An important fact is that lower-level scopes have access to variables declared in higher-level scopes. This is why all scripts and calculations have access to $$ variables, and all calculations in a script have access to the script’s $ variables.
At the time I wrote the original blog post, the Let() function was pretty much the only place where you could create a new variable inside a calculation (any of the three types) which is why I named that scope “calculation”.
It’s time to refine that 3rd level a bit and call it “Function variables”. The reason for this follow-up is the new While() function that is introduced in FileMaker 18.
The anatomy of a While() function call breaks down into four parts:
- Initialization or setup
- Condition that determines how many times the While() function will loop (While the condition is true, execute the logic)
- Logic; one or more calculation statements that will be executed in every iteration
- The output for the function call
When we look at the initialization part of the While() function, we’re actually declaring variables: “i” and “out”. Similar to what we can do in a Let() function call:
Let( [ i = 0 ; Out = “something” ]; Out & “-“ & i )
Given that we now have two functions, Let() and While() that can create variables, what happens when we use both functions in one calculation? What happens to a variable that is created in one function call and used in another function call within the same calculation?
The help for the While() function spends some considerable time on this, which underlines the importance of properly understanding its implications.
Let’s start with this modified version of the example in the online help.
This While() is perfectly constructed, all of the variables it needs are declared, the condition is clear (it will run 3 times), and the logic will increase both the _total and the _count variables and output the _total variable when done. I’m using my preferred prefix (“_”) for the function variables to make sure there is no confusion about what they are.
While ( [ _count = 1 ; _total = 0 ; $a = 25 ; $a = 50 ; $a = 75 ] ; _count <= 3 ; [ _total = _total + $a[_count] ; _count = _count + 1 ] ; _total )
The fact that we also declare a script-scoped variable $a inside the While() has one big consequence: _count and _total will be wiped out when the While() function ends, but the $a variable with its three repeats will maintain their value.
The nested Let()/While() calculation below will produce the same result:
Let( [ $a = 25 ; $a = 50 ; $a = 75 ]; While ( [ _count = 1 ; _total = 0 ] ; _count <= 3 ; [ _total = _total + $a[_count] ; _count = _count + 1 ] ; _total ) )
Why does it produce the same result? Because the Let() function now declares the script-scoped $a, and that script scope supersedes the function scope while() has automatic access to those variables.
What happens when we move one of the While() function variables to the Let()? See the red highlighted change:
Let( [ $a = 25 ; $a = 50 ; $a = 75 ; _count = 1 ]; While ( [ _total = 0 ] ; _count <= 3 ; [ _total = _total + $a[_count] ; _count = _count + 1 ] ; _total ) )
We now declare the _count variable in the Let() and use it in the While(). The result, however, may be surprising; when we execute this calculation the result will be “?”, indicating that the recursion limit was exceeded. In other words, the While() function did not execute the logic block three times, it looped 50,000 times and still could not satisfy the exit condition (“_count <= 3”) despite our logic block clearly incrementing our _count variable on each iteration.
The crux here is a very particular behavior of the While() function: the variables you manipulate inside the logic block will only maintain their value if they are declared in the initialization block. What this means is that _count will be incremented in the logic block, but on the next iteration, it starts with its initial value, not the previously incremented value. It will never increment to three.
That may lead us to think that the While() function has no access to the variables from the Let() function. But it does.
Consider this example:
- On line 8 we are declaring a _name variable, in the Let() scope.</li<
- On line 18, inside the While() scope we are modifying that _name variable by appending an exclamation mark. Note that the _name variable is not defined in the While() scope.
- On line 22 we output a string from the While() function that includes the value of the _name function.
- And finally on line 24 the Let() function appends its own string to the outcome of the While() function: it adds the value of the _name variable as the Let() function knows it.
The result is this:
The result shows that the While() did properly execute 3 times, as we expected because it is properly constructed with the _count variable declared in its initialization block.
But clearly, the While() function has access to the Let()’s _name variable because it modified it by adding the “!”. We only get one “!” appended though, not three as we might expect. That is because of the behavior we outlined earlier; the modified value of _name does not get retained with each iteration, so each iteration starts with “wim” and adds an “!”. When the While() function ends, it has “wim!” as the final value and uses that in its output.
But when the While() ends and control is passed back to the Let() function, While()’s copy of the _name variable is terminated. And Let() does not know (or care) that While() modified it. As far as Let() is concerned the value is still “wim”.
In conclusion, it is safe to say that scope just got a little more complex and that there are three main things to remember:
- A nested function has access to the function variables declared in its ‘parent’ or ‘outer’ scope.
- A parent function has no access to the modified variable from a nested function.
- If you need to modify a variable inside a While() with each iteration of the loop, you have to declare it in its initialization block. You cannot use an inherited variable.
As always: feel free to leave questions and comments here on the blog post or find me on community.filemaker.com.