Scope of $variable in Let statement in Calculated Field

I thought I had discovered a bug with JSON, but it turns out that I was wrong.

I have a script like this:

  Set Variable $N = ValueCount(...)  // $N = 44
  [... do some calculations ...]
  Set Field Table::Foobar
  // Suddenly, $N = 2

What's going on is that this table has a calculated field:

Table::CalculatedField = Let(
[
  $N = Get(CalculationRepetitionNumber)
]; 

 "foobar repetition " & $N & " is  " & GetRepetition(Table::Foobar; $N)
)

So, apparently the "$N" variable in the script, and the "$N" variable in the calculated field are interfering with each other; when I set the value of Table::Foobar, Table::CalculatedField updates, which sets $N to a different value, causing problems.

Has this always been the case? Clearly, based on my scripts and calculations, I was under the impression that in a calculation field, variables and $variables were scoped locally to the calculation and were thus the same.

If this isn't the case, then I'm really confused!

1 Like

The documentation is not crystal clear to me:

The Let function sets the variables from left to right. You can use previously defined variables (for example, function variables defined earlier in the Let function or local and global variables that you defined with the Set Variable script step) to define new variable values.

You can also nest one Let function within another. If you use a previously defined function variable within a nested Let function, the function variable has scope only within the nested function (as if you had defined a completely unique variable). See example 2, below.

It sounds as if the first Let() function does not create a new scope, but if you have a nested Let() function that reuses the same $variable names, then these do get a new scope?

That sounds bad.

I suggest checking your assumptions in a clean file that has nothing but the table, with the fields needed for the script, and the script.

You could try naming the custom function's variable differently. Preferably drop the '$' identifier. If this yields a different result, this hints at a scope issue.

2 Likes

Further testing suggests it's a very simple issue - in a script, if something (such as a Set Field step) causes another calculated field to update, any $variables used in the calculated field's calculation will be in the same scope as $variables used in the script. It doesn't require repeating fields or JSON or any thing fancy.

  • if you use regular variables (such as 'N' instead of $N) in the calculation field, it doesn't happen
  • if the calcuation field is not stored, it doesn't happen (presumably because it doesn't update when you do the Set Field step?)
  • this behavior happens both in FM18 and 19, so it doesn't appear to be new.

This seems like a very subtle and devious trap, because:

  • when editing scripts, the Set Variable script step automatically adds $ to variable names -- if you enter 'N' you get '$N'
  • because of this, it's natural to assume that $N variables are the same as non variables without the dollarsign, and both are local to the enclosing context
  • if you then copy a calculation from your script to a calculated field, it's easy to just copy & paste, and then you are using $ variables in the field calculation
  • becuase of how it works in scripts, I naturally assumed "$" variables are local to the field calculation. This tuns out to be wrong.

You should note that $var variables are scoped to scripts. You can define a $var variable in a let statement in a field calculation, however when a script runs with the same $var variable and the table is in context, both are one and the same.

4 Likes

The set variable step always adds a $ character to the variable name. That's the recommended scope. You are able to add a second $ character to create a global variable. You cannot remove all the $ characters. When you do so, a $ character will be added to the variable name. This makes sense. A variable without a $ prefix can be created within calculations that use the LET and WHILE functions. Any variable that you set with the Set Variable script step must be local or global to survive beyond it's own definition and be used in the script.

Having said all of that, I've just experimented and found exactly the problem that you are describing. This seems like a nasty bug to me. Will you report it?

My test is attached

VarLeaks.fmp12 (292 KB)

@Malcolm - thanks!

Here's my project file:

variable_scope.fmp12 (368 KB)

I'm still unsure about this - the behavior is really unexpected to me, but maybe everyone knows that's how it is supposed to work? I appreciate others saying they find it weird too...

This is a bug that could have serious consequences. No-one wants to have values from field calculations bubbling up into their scripts.

It is an excellent reason to ensure that you limit the scope of your variables as tightly as possible. In your case, the variable $N can be more tightly scoped by removing the $ character, making it a calculation variable.

1 Like

reminds me of this thread:

3 Likes

FMScript handles scope not the same way other known languages like C, C++, C#, Swift, Java etc. do.
The scope of a $-scoped variable is the executed script, that of a $$-scoped variable the executed file and variables declared in functions that have no $ or $$ scope designator have the function as scope. In the above-mentioned languages, functions do not know variables that have been declared in another function. The compiler guarantees that scope is respected and there is no interference between functions.
FMScript does not enforce variable declaration and lets live $-scoped variables in functions. The responsibility for controlling scope between script and function lies entirely with the developer, the script engine does not throw a warning.
Declaration of variables and strong scope are missing in FM since many years.
Avoiding the use of script-scoped $variables in functions is a good workaround.

2 Likes

This can also happen when using the Data Viewer: Overriding Variables using the Data Viewer

And applies to Custom Functions as well.

hey @MonkeybreadSoftware - would it be possible to create a feature to audit our calc fields and scripts?

Warning: Table::FIeld is a calculated field which uses $variable names, which could have unexpected side effects. Consider rewriting the calcuation using plain variables instead.

if you see a custom function with a local variable assigned or embedded in a statement / expression within then run as fast as you can!

3 Likes

Or wrestle the beast :t_rex:

1 Like

All this discussion of scope is perhaps confusing people. Here's a very simple way to describe what actually happens:
When a $variable is defined, it is shared across EVERYTHING while it is defined.
When a SCRIPT defines it, it goes away when that script finishes running.
When something else defines it (Field calc, layout conditional formatting, button-bar label calc, etc, etc.), it is defined in what I've seen people call "script-zero" scope: In other words, EVERYTHING sees it, and it sticks around while that object-with-a-calculation is evaluating it.

So, moral of the story: If you do not NEED (and know why you need) a $variable in a Let or While, do not include the $ symbol - use truly calculation-local variable names.
If you DO use $variables outside of an actual script, use them VERY carefully. Give them VERY UNIQUE names.

I've seen custom functions, which could be run in all kinds of contexts outside of scripts, use names like $num to do some kind of across-recursion-level processing of info. If you must use a custom function like that, rename its variables to $_NAME_OF_CUSTOM_FUNCTION__whatever_they_named_it. Better is to avoid those custom functions, if you can.

6 Likes

Thank you @DanShockley

Also, I wonder about this:

To clarify, I almost never use $$ variables, but I assume these act like global fields, e.g. global to the file, but global only on a per-session basis (e.g. User A's $$foobar variable will not affect user B's $$foobar varaible). I would assume this keeps $$variables isolated across Scripts that are running in PSOS mode?

Correct. $$variables are unique to each user.

What about if the same user is connected to the database twice? :slight_smile:

:thinking: Interesting

I assume user sessions are independent of each other. I know people using iPad and desktop at the same time.

1 Like

$$variables are unique to a file, from the moment it is opened to the moment it is closed in a client. Client, here, means FileMaker Pro, FileMaker Go, FileMaker WebDirect or a server-side session invoked via PSOS or a scheduled server script.

2 Likes