This help page does not mention local variables at all. What it does say is “It’s a good idea to create global variables for use as merge variables.”
That could mean: the issues that arise with local variables are too complicated to explain easily, so please use globals for this purpose.
Of course, many of the issues we are seeing affect globals too. The difference being that we are aware of them and expect them n the global space. We expect locals to be tightly scoped.
This is not a recommendation to avoid an undocumented feature. This is a recommendation to avoid a trap where the layout doesn't display a $ variable because the script that defines the variable is not running.
Up until v18's introduction of the While() function, there was a good use of $localVars in recursive custom functions: maintaining the recursion level so that you didn't need to have a separate function to set up an initial state and then recurse until reaching a certain value. Ray Cologon had a good example of this in his audit logging approach.
But as I discovered in my own early attempts, the variable persisted across multiple calls to the custom function so it needed to be cleared as part of the exit condition. From memory (it's quite some years ago now) the $localVar would appear in the Current tab of the Data Viewer even after the CF completed its execution; I doubt that behaviour would have changed.
I suspect that the scope of such variables is "local" to the class of object that defines them - each class maintains its own stack with its own rules for persistence, so scripts clear their stack after each execution but custom functions (IIRC) are evaluated on record load and need to persist for as long as the record is active (not open/locked, but active). $localVars defined in layout objects are probably evaluated on layout mode entry.
As a complete guess, I think that the stacks are all implemented as contiguous memory and either there is no clear boundary indicator or the stack management code ignores any such indicator. The Data Viewer, I think, is stripping any namespace information from the variable names (if it exists) with a goal of reducing complexity and potential sources of confusion and relying on the general programming advice of "don't re-use variable names" to avoid name collisions.
Best advice is to follow precepts from 30+ years ago: always explicitly set your variables to known values before using them, and explicitly set a namespace to differentiate your variables (my usual practice is a 3-letter lower-case abbreviation followed by Camel Case variable names).
No need for $vars in custom functions. Use unification then you don’t need helper functions. Anything else violates side effect free functional programming paradigm. See LISP or PROLOG languages…
There never was any need to use $ variables in custom functions, even before the introduction of the While () function. Custom functions have always had the ability to pass arguments to and receive results from sub-functions and recursive functions.
In my opinion, using $ variables in custom functions is asking for trouble. The scope of a $ variable is not the function nor is it the class of the calling object. The need to clear such variables on exit is too easy to miss. Use such a function in field definitions and the variable's value will be shared across all fields and all records in a file. Use such a function in scripts and you will be surprised at the results. Use such functions in long-lived solutions touched by many developers (and let's add citizen developers for fun) and you are in for a mess.
There are performance benefits if the data being manipulated is above ~10KB. But one could certainly use a $$ variable instead of a $ variable. And it's habit to use locally-scoped variables because when the scope is truly localised to the current code block the memory is released at the end of the code execution.
The confusion is arising because FileMaker is not explicit about scope definition for the $ variables. And in the face of this uncertainty, we're better off using explicitly namespaced $$ vars instead and explicitly setting those vars before use (and cleaning up afterwards).