Understanding Definitional Scoping

I just started learning Red/Rebol, and I was having trouble understanding the scoping rules. I did read the Stack Overflow and Bindology, and I think I somewhat understand, but I'm not sure! I'd be grateful if someone can confirm the my deductions about the following code from the SE post:

rebol []

a: 1

func-1: func [] [a]

inner: context [
    a: 2
    func-2: func [] [a]
    func-3: func [/local a] [a: 3 func-1]
]

reduce [func-1 inner/func-2 inner/func-3]
  1. Compile-time: First, when the code is compiled, a list of top-level names is created. (a, func-1, inner)
  2. Load-time: When the code is loaded, context A is created with those symbols as members. Also, the entire code is walked through, and every occurrence of those symbols is bound to these entries. (e.g., a inside func-2 would be bound to the top-level a)
  3. Run-time: When a: 1 is executed, the value 1 is stored in the a's slot in the context.
  4. The func keyword after func-1: creates a new function, and assigns it to func-1. However, it leaves the binding of a intact.
  5. The context keyword after inner: creates a new context B, walks over all the block, collecting all new symbols, and inserts those in the new object.
  6. a: 2 assigns value 2 in a's slot in the new context.
  7. func after func-2: creates a function while keeping a's new binding intact, and assigns it to func-2's slot.
  8. func after func-3: creates a function and creates a new context C in which a is inserted and bound.
  9. Upon execution of reduce, func-1 returns 1 from context A, func-2 return 2 from context B, and func-3 executes func-1 which returns 1 from context A again.

So, now, a few questions:

  • Is the above correct? If it is,
  • How does the context keyword determine which set-words it should create a slot for? i.e.,
    • if a: 2 didn't exist, will it still create the slot for a due to a: 3 deep inside?
    • What about if a:3 didn't exist either? Will the new context contain a slot for a just due to a inside func-2's body?
  • Did context bind a in [/local a] too, before func was executed?
  • When a new context is created, does it copy existing symbols or bindings from the older context? If not,
  • Is there a parent-child relationship between contexts, or are they free-standing? Given a context, can I chase some pointer to its parent or child?

Sorry for so many questions, I think I am getting extra confused since I already have programming experience and need to unlearn some stuff before being able to grok Rebol.

Thank you.

1 Like

Hello there... thanks for your question, and no need to be sorry. Questions are well warranted.

(Note that I take no responsibility for the madness that is Rebol--I just happen to hack on a derivative of the open source codebase because I have found various interesting aspects about it. :-P)

Rebol is interpreted--not compiled. (Red attempts to do some compilation, and I'm unclear on how that works...you will have to ask them.)

It's probably dumber than you think. Not only is there no "compile-time", "load-time" isn't where the binding happens. It all happens at run time.

CONTEXT is an alias in historical Rebol for MAKE OBJECT! and the behavior of that is that it only searches one level of depth symbolically for SET-WORD!s to gather.

Deeper walks are possible. The concept behind R3-Alpha and Red's FUNCTION is that it searches deeply the body for any SET-WORD!s and assumes they should be local variables. This creates the problems you would expect: a nested function's locals will be picked up as locals in the outer function, and SET-WORD!s used in the body incidentally as object members or for dialect purposes will also be picked up. This is why Ren-C has LET and does runtime variable instantiation which runs in a wave (virtual binding), you can't sensibly calculate a static concept of what symbols are variables or not.

Ren-C is quite different from Rebol, and so answers will vary significantly, if you find that you care about the difference.

No, CONTEXT was merely looking for symbolic SET-WORD! tokens at one level of depth underneath it. a is a plain WORD!, and it's in a block one level deep, so that's two strikes.

Contexts inherit keylists as an implementation detail for efficiency, and it's weaker than v8 hidden classes.

Javascript Hidden Classes and Inline Caching in V8

I have thought hidden classes might be better, at times, but then again that would complicate the code. And part of the point is to let everyone understand the stack. It's a thought experiment.

Rebol was written to be a brutishly simple C '89 codebase and a lot of the "interesting" aspects were higher-order studies on top of an interpreter that worked better than you would think for how dumb it was.

My take is to call it "Amish Programming", you may find my talk at the 2019 conference useful if you're getting your bearings.

Talks | Rebol [2019]

1 Like