What if Blocks Had Scopes (But Not Individual Words)?

Apologies for butting into this discussion as someone completely new to the project, but I think I have something worth mentioning in this context:

Specifically, I’ve recently been looking into how scoping is implemented in R. I’m no R expert, but my understanding is that scopes in R are first-class values — just a certain kind of object expressing a map from variables to values. To implement lexical scoping, each scope has a pointer to a parent scope.

What’s quite interesting about R is that, like Rebol, it extensively uses unevaluated expressions as a substitute for macros. The implementation is different — putting it in Lisp terms, everything in R is an fexpr — but the end result is reasonably similar. To my understanding, each expression has an associated scope, and by manipulating the scope one can achieve some quite remarkable things (e.g. as done by the tidyverse).

So, in that light, here’s something I came up with the other day: a Rebol-like language where, instead of each word being bound to a variable, we have each block being bound to a scope. This does sacrifice a little bit of flexibility, since individual words can no longer be re-bound — instead, all words with the same spelling must be re-bound at once. But this is probably a desirable feature. And it preserves the much-vaunted ‘definitional scoping‘, since functions can still modify the scopes of blocks passed as arguments. Indeed, this may even make it easier to rebind words, since one can simply make a new scope and set its parent to the previous scope!

That being said, I haven’t very extensively used Rebol (or for that matter Red or Ren-C), and there’s probably stuff I’m missing here. Do you have any thoughts on this?

1 Like

Hello, welcome! Feel free to post an introduction if you like.

Rebol has a GET function which has historically operated as single-arity. You pass it a WORD!, and "bound words" have context attached which allow them to be looked up: get word

If you have an unbound word, then you could use the arity-2 operation IN to manufacture a word with context. So (get (in context word)) or simply get in context word.

If blocks are able to carry some form of scoping information, it doesn't seem all that useful to prohibit words from carrying this information as a convenience. (It would also break all existing code to make GET arity-2...which I'm not afraid to do if it improves things, but I don't see how disabling words from holding scope would be an improvement.)

So in the model of blocks being "in control" of imbuing words with scope, you'd simply have words carrying the scope of wherever they are last picked out of. Then poking an isolated word into a block would lose the scope it was carrying, and it would assume the context of the new location.

But there are limitations to having blocks (and groups) retain their scopes with words deferring to whatever blocks (or groups) they're put under. One choice would be that you could say that unbound words would act this way... making it a pretty easy behavior to get e.g. append block unbind word. Then the default behavior would be to preserve whatever binding was traveling with the word.

This is something I cover in Rebol and Scopes: Well, Why Not? (which is worth reading if you haven't already). I have experimented with mechanisms that build chains of scope, which are indeed carried by the blocks:

"FUNC stows the body block away in an ACTION! that it generates. Later when it gets invoked, it creates a FRAME! with return and x in it...and puts that in a chain with the module context. So upon entry to the function body, that body is being executed with a specifier that looks in the frame first (would find that x) and then in the module second (would find global and foo ). This compound specifier is what the evaluator state initially has for that body block."

I should point out that it's block values that carry these virtualized scopes, not block series. So each function invocation--even though it's using the same arrays of items in memory--gets its own "view" on that data. Making a BLOCK! value as seen by the user a (data, index, scope) tuple under the hood.

In any case, it would be good to detail any proposal for giving blocks scope as how it contrasts to what I lay out in that post...

That's interesting to know. If you're familiar with R, then posting notable examples of its mechanisms in the Foreign Inspiration category would be quite helpful...I'd be interested to see.

1 Like

Ah, I hadn’t realised you’d considered this already. Good to know, thanks! (Especially since I’ve been considering this scoping mechanism for a programming language of my own.) I’ll read it when I get time later today.

I’m not hugely familiar with R… but like Rebol, examples of this mechanism abound, so it shouldn’t be difficult to put together.a post on it.

And done!