Having local variables be invisible to the outside of a function is something that has been important in Ren-C since the beginning.
Superficially, we want to protect against the following kind of bug, e.g. that Red has inherited from Rebol2 and R3-Alpha:
red>> maybe-format-drive: func [user password /local permissions] [ if check-supervisor user password [ permissions: <supervisor> ] if permissions = <supervisor> [ print "Formatting hard drive" ] ]
The code above is assuming that permissions will default to NONE!. But the problem is that you can slip the permissions in via the /LOCAL refinement:
red>> maybe-format-hard-drive/local "mallory" "password" <supervisor>
This is why Ren-C makes sure all true locals are set to
~undefined~ when a function runs. While being undefined makes them more prickly than BLANK! or NULL, you can default them using DEFAULT.
But Wanting To Hide Things has More Motivation Than That
Clearly we're not dealing with a very "secure" language model. So there's a bigger reason.
It's important for us to contractually establish that the locals are none of the caller's business. Because once we have this information hiding, it offers the potential for name reuse.
Remember AUGMENT? It lets you add parameters to a function frame. This lets you do things like make a version of SWITCH that has /DEFAULT but inherits the interface of SWITCH and runs all in the same memory block.
But what if just incidentally, the implementation of SWITCH used a local variable called "default"? Then you'd wind up with a frame with two keys with the same name.
Having a frame with two keys that have the same name sounds like a pretty big problem for binding...unless there were some way to tell which names were in effect for a FRAME! based on which phase of a function composition it was in.
This is where frame phasing comes in.
Quick Demo of FRAME! Phasing
foo: func [public <local> private] [ private: 304 return binding of 'public ; return a FRAME! with the internal view ] >> f-outside: make frame! :foo == make frame! [ public: ~undefined~ ; from the outside, you don't see `private` ] >> f-outside/public: 1020 >> f-inside: do f-outside ; Note: DO kills the outside view, unless you copy == make frame! [ return: 'make action! [[@value /vanishable] [...]] public: 1020 private: 304 ]
We're seeing two different views of the same memory. The outside view is unaware of the locals. The inside view reveals them.
Important to observe is that what view we see doesn't depend on which stack level we're at, but the bits of the value in our hand.
Now, how about that augmentation? Let's add a /PRIVATE refinement to a layer on top of FOO that uses the same frame:
>> f-prelude: null >> bar: adapt augment 'foo [/private [tag!]] [ f-prelude: binding of 'private ] >> f-outside: make frame! :bar >> f-outside/public: 1020 >> f-outside/private: <different!> >> f-inside: do f-outside == make frame! [ return: 'make action! [[@value /vanishable] [...]] public: 1020 private: 304 ] >> f-prelude == make frame! [ public: 1020 private: <different!> ]
f-outside) are all the same frame, which reveal a different PRIVATE field based on the phase.
This addresses Ren-C's oldest open issue, from Feb 2017. Which I hope to be able to close shortly.
How Does It Work?
I've said time and again that a value cell is the size of 4 platform-pointers. That's a rule of the game.
Like all cells, a FRAME! cell needs to give up one of those for the header...to say (among other things) that it's a frame and not an integer, etc.
Next you have a binding. This binding is important for instance when you MAKE a FRAME! for a RETURN function. It needs to know where to return to.
Then you need to have a "varlist", to point at the list of values that the frame's variables currently hold.
The final slot is either a frame phase or a frame label
If the phase slot is a label, you assume that the FRAME! value you are looking at has no special privileges to see locals or anything. It gets its keylist from the archetype of the frame...e.g. the pointer held by the varlist itself. In this particular no-phase case, all locals and specialized-out values are hidden from the phase.
If the phase slot is not a label, then that's the phase...and the parameters of the action are used as the keylist. Locals are counted since there is a phase...but "sealed" variables are not (these are what would have been considered "local" by the function that this phase derived from.)
The upshot of this is that many operations which used to just be able to take a varlist now need to take a whole ANY-CONTEXT! value, so the phase information is present. Which fields are in play due to the phase is important not just for molding, but also for binding and lookup.