And just to be clear: they should most definitely not be. But it is tempting to make them so. What is not always clearly understood is why it is not a good idea to declare variables “out of scope”.
It’s all about Scope, Lifetime and Visibility of variables. Those topics are not unique to FM, they apply to all coding environments and the same best practices apply to all of them.
The directive is that you only declare the variable for the scope that it needs to be used in. FM has 3 broad scopes:
- the file
- a script
- a calculation (this is murky in FM because there are so many places where calculations can be specified: a field def, script steps, layout elements like visibility, conditional formats, web viewers, charts, etc…). Some calculations are explicitly controlled by us (a Set Field script step for instance), but some calculations will fire outside of our control (for instance when one needs to display the result of an unstored calc).
FileMaker has three types of variables for use in those different scopes:
- global variables: they have a double dollar sign prefix ($$myvar) and once declared they will retain their value until the FileMaker file closes (or some scripted or calculated action clears their value). You’d typically use the “Set Variable” script step to declare them.
- local variables: they have a singe dollar sign prefix ($myvar), these variables are typically declared in a script and retain their value until the end of the script (or until a scripted or calculated action clears their value). You’d typically use the “Set Variable” script step to declare them.
- calculation variables: there is no preset prefix or naming convention and I typically use an underscore for them (_myvar), these varaibles are declared by a Let() function call and only retain their value until the calculation is calculated. The Let() function can also be used to declare local and global variables.
Ideally you want your variables to have an automatic lifetime in the sense that when the scope closes the variables expire. The variables are cleared automatically by the program. FM does that for all 3 scopes: $$vars get cleared when you close a file, $vars get cleared when the script ends and your Let() variables get cleared when the calculation ends.
Where possible you want the program to clean up after itself and avoid having to do it yourself in your code. Taking advantage of the automatic expiration of variables significantly reduces the risk of errors and confusion. It should also – at least theoretically – reduce the memory resources that your solution needs.
This is typically where you run into problems by not limiting variables to their scope and not taking advantage of the automatic lifetime management. The value of a $$var is available in every script, in every calculation. The value of a $var can be used throughout the duration of any script (even when you declare it part of a calculation outside of a script!). When you declare a $$variable in a script so that another script can also access it, it will still hold its value when your scripted flow is done and you did not explicitly clear out the variable.
That “visible when it should not be” can clearly cause confusion but it can also be a security risk. Variable-leftovers can be visible in the data viewer when you don’t want them to.
If I declare a local variable $otherVar or a global variable $$var a in a Let() function in a calculation for instance, like this:
let( [ $var = "something" ; $var = "something else" ; _var = "lorem ispum" ]; $var & " - " & $var & " - " & _var )
Then both $$var and $otherVar will still be visible when the calculation ends. $$var will be around until I close the file. “_var” however will just last for the duration of the calculation and will not be available past that point.
If I really only needed $$var or $var to get to the calculation’s end result, then I have to remember to clear them out, or risk accidental use of those variables values. In most cases it is better to not declare $ and $$ variables in the Let() function in the first place, use another notation like _var and _otherVar (there are no strict rules about naming calculation variables).
Say that in my script I had already declared $var and set it to “my value”. Later in the script I do (or FM does) something that fires the calculation above. Then $var will contain “something else” and not “my value”. That may be your intention but it may not be. Since there are so many places where we can use calculations, using them to declare local and global variables does carry the risk of changing values of the same variables declared elsewhere.
The thing to remember is that when you use variables outside of their scope then the burden is on you to make sure they get cleared when they are not needed. If you do not, then it becomes really hard to troubleshoot and figure out when variables have a meaningful value or when their data is stale and meaningless.
So when do you use what type of variable? If you are in a script and you need to pass on some piece of data to another script, the ideal thing to do is to pass that piece or pieces of data on to the next script as a script parameter where they can be parsed as local variables for use in that script. This way, both scripts will clean up after themselves. When you need to revisit the scripted flow a few months later you do not have to worry about what happens to the variables in scripts that either call or get called from your script.
It is easy to side-step some of this extra work and use global variables instead. I am certainly not saying to not use global variables ever. But use them only where you need to and nowhere else. For layout merge variables for instance: absolutely, that’s the way to go. But for making info available between scripts: not so much. The preferred approach is to pass parameters to the other script.
Passing one or more multiple parameters between scripts and especially returning a result from a script to be used in a a calling script is easy to implement. A number of techniques have been developed over the years and take all the work out of it. One approach can be found at ModularFileMaker.org.
If you use local variables and use some of the available techniques for passing parameters to the subscripts you can be assured that all variables will get cleared when the process is done. Automatically. Easy.
11 thoughts on “All Variables Should Be Global… Or Not.”
Thanks for another quality post.
If you have not already, you might want to check out Window Controller…
…very clever stuff from Mikhail Edoshin.
One of the things that Mikhail Edoshin does (that I have not seen done elsewhere) is
pass a $var from one field auto-enter calc to another.
Mikhail Edoshin also has some variable names with spaces in them which I did not know one could do (and would not recommend!)
That said, Window Controller is filled with cool stuff and well worth a look.
Once again, thanks for the quality post.
Thanks, Tony. I will check it out!
The thread on FMforums.com that prompted the blog post:
The conversation goes on…
Nice post Wim!
The module for passing parameters at modularFileMaker.org can be found here
Very good overview of the issue. Thanks for that. I would like to submit one teeny-tiny clarification, though. “Calculation Variables” do not, in fact, survive until the end of the calculation; they last only in the construct of the Let function.
This will not work:
Let ( a = 1 ; a )
Let ( b = a + 1 ; b )
When you go to exit the calc editor, FileMaker will highlight the “a” in the second Let and tell you “The table cannot be found.”
Most people would seldom, if ever, put more than one Let in a calculation (although I have seen it done for valid reasons) so this is not an issue that would come up very often.
Still, in an otherwise spot-on post, it’s probably appropriate to clarify this. Some may want to use this knowledge in a constructive way, and others may simply want to know to why their beautiful pages-long calculation fails.
One more point that is also not so obvious to those newer to the platform is that Locally-Scoped variables not only do not outlast the script, they are not available to subscripts either. So it is not a matter of lifetime, that particular “feature” is a matter of an inflexibly narrow implementation of that “scope” that you were talking about.
I am frequently disappointed that I cannot access a script-scoped variable in a sub-script. Splitting out subscripts to keep main scripts succinct requires extra work, as a result, to keep the required data available. If you keep everything in your main script you don’t have to worry about that, but then your main scripts are less modular and more verbose. That is certainly why there are a several well-thought-out methods to pass data between scripts in the community.
Thanks for the clarification on the multiple Let()s in one calc, Jonathan.
This information iis invaluable. Where can I find out more?
Anything specific that you are after?
I know this article was done 6 years ago but it’s still a great read even now. I’m currently working on a application I inherited that has 5 global tables to do various things and in total there are 473 global fields used throughout the application. 🙁 I know right – Grrrrrrrrrrr.
Anyway, as always, great job Wim. Big fan too by the way – love your “CURL custom functions” database. Keep up the great work and kudos for all you do for the Filemaker community.