This is good intuition. I tend to use this kind of idea in code these days. Here's an example:
($define! tree ($branch
(dog ($branch
(bark ($branch
(loudly ($lambda () "WOOF!"))
(softly ($lambda () "woof"))))
(run ($branch
(fast ())
(slow ())))))
(cat ($branch
(meow ($lambda () "meow!"))
(purr ())
(run ())))))
($walk tree ($walk dog ($walk bark (loudly)))
=> "WOOF!"
If one attempted to just say (loudly), an error would be raised because loudly only exists in the context of bark, which only exists in the context of dog, etc. We walk through the tree to find this context, then run the function (loudly).
In the above Kernel code, the context is called an environment, and environments are first class. The evaluator takes an expression o and an environment e as arguments, and it is said that "o is evaluated in e".
The tree is basically described by construcing new environments, where for example, the symbol "dog" is bound to another environment containing the bindings "bark" and "run". I've used the word "$branch" in the example for simplicity, but this term already exists in Kernel under another name.
($define! $branch $bindings->environment)
$walk is implemented by combining the current environment with the one specified as its first operand, then the second operand is evaluated in the combined environment.
($define! $walk
($vau (env expr) dynenv
(eval expr (make-environment (eval env dynenv) dynenv))))
Programming this way can be pretty fun, as you're not restricted by some arbitrary "impressive sounding names" for accessing environments in restricted ways. You can do things the way you want, and it's fairly trivial to implement your own object systems.
I've termed it environment-oriented-programming. I basically use it as a means to implement OOP, generics, records, algebraic data types and whatnot. When combined with the use of other Kernel features like encapsulation types, it can be used to implement interesting type systems.