Bingo. Think I hit this on the nose almost 4 years ago... it just took a while for the tech to catch up!
But what we were missing at the time was a way to deal with how "methods" when FUNCs (or METHs) inside such a constrained construction had no way to reach the object to know what words it had. Pure Virtual Binding gives us the resources we need to create a whole new way to access members:
Since changing MAKE OBJECT! would be too far-reaching a change at the moment, I've retaken CONSTRUCT for this shallower version:
>> x: 10
>> obj: construct [x: x * 2, f: func [] [print ["x is" x "and .x is" .x]]]
== make object! [
x: 20
f: ~#[frame! []]~
]
>> obj.f
x is 10 and .x is 20
The only ANY-WORD!s in the block passed to CONSTRUCT that are bound to the object being generated are the SET-WORD!s at the top level. Everything else trickles down as normal in pure virtual binding. So it sees X inside that block as it is defined outside.
Hence the FUNC of F does not see the object's X being created. But the twist is, that when you say OBJ.F, the object is folded into the function cell being executed. That is then folded into the environment during execution. And the tuples starting with blanks, rendering as .x
or .x.y
or whatever, they don't search the normal binding for the symbol but they look in that "coupling" binding.
The benefits to most code by using this pattern are tremendous!
-
You can refer to same-name variables from outside the CONSTRUCT inside it without a COMPOSE
-
But should you find the need to COMPOSE material in from another place, you don't get incidental contamination of the binding of that material from the fields of the object being created
-
AND you can tell when reading a function's body what things are object members, in a lightweight and general fashion, using the TUPLE! datatype which is already handled by places that need to look up variables.
If we wanted to, we could say that CONSTRUCT (or MAKE OBJECT! or {...} or whatever this is) would slip the object's identity into a virtual LET binding under a name like self
or this
, so that it could be mentioned freely inside the code. But that wouldn't have to be a field cluttering the object itself!
>> x: 10
>> obj: construct [x: 2 x: this.x * 3 f: func [] [print ["x is" x "and .x is" .x]]]
== make object! [
x: 6
f: ~#[frame! []]~
]
>> obj.f
x is 10 and .x is 6
Bingo, again. Another prognostication from 3+ years ago which needed the rest of the system to catch up to make it feasible.
When the system can function as people expect when most all the code they're working with is unbound at the block level, that's a very good sign.
foo: func [x] [
code: copy [add x]
append code to word! "x"
print ["Doubled:" do code]
]
>> foo 10
Doubled: 20
I'm now far, far more confident in where binding is at... and it points back to these early realizations.
It's definitely going to require people getting comfortable with new ideas... old code just won't work (e.g. get 'x would be getting something with no added binding, so it has to be get $x or get in [] 'x or whatever). People have to understand why.
For those who just want to load %file.jpg they may think they don't have to care, but the features and dialects they can experience will be limited by the feasibility of others writing those for them... and writing C and Red/System code to avoid composability problems can only get you so far.