.WORD as Member Selection

Pure Virtual Binding opened the door to this, so I tried it.

I made the CONSTRUCT primitive a version of MAKE OBJECT! that didn't bind everything inside the block. It only binds the SET-WORDs at the top level, so you can do things like this:

>> x: 10

>> construct [x: x + 10]
== make object! [x: 20]

Then, my "naive" first approach to enable .WORD was simply to make tuple selection stow the object that it picked out of in the function value returned.

>> global: ~

>> obj: construct [x: 10, set $global /foo: does [.x]]

>> obj/x
== 10

>> /test: obj.x/
== ~#[frame! []]~  ; anti

>> test
== 10

>> global
** Error: .x has no object coupling in effect

In the case where you're extracting the action value to store in TEST, the value you get has a pointer to the OBJ object embedded inside it. So it knows how to run it.

But the global variable that was set didn't get that. It just has the uncoupled function. There's a way for you to fix that:

 >> /global: couple global/ obj

 >> global
 == 10

We could make that easier, by letting you pass in a variable to update instead of an "immediate" ACTION! value, e.g. couple $global obj. (Being able to do both might be interesting for more functions of this nature.)

In any case, I don't really have a problem with the fact that doing something like that gives you an "uncoupled" method. That seems fine.

But other things raise questions, some easier to address than others.

What About Enumerations?

obj: make object! [x: 10, /foo: lambda [y] [.x + y]]

for-each [key ~/val] [  ; proposed notation for "maybe action"
    if action? val/~ [  ; proposed notation for "don't run, maybe action"
        print [val 1000]
    ]
]

If this is going to work, then the enumeration behind FOR-EACH has to add the coupling.

When To Override?

The current idea is that once an action gets a coupling, it sticks.

>> obj1: make object! [x: 10, /foo: does [.x]]

>> obj2: make object! [x: 20, foo: ~]

>> /obj2.foo: obj1.foo/

>> obj2/foo
== 10

If you don't want this, you'd have to store an UNCOUPLE-d version of the function.

>> /obj2.foo: uncouple obj1.foo/

>> obj2/foo
== 20

I don't really have a problem with that.

Should ALL Functions Get Couplings?

For starters, METHOD and FUNCTION were synonyms. All actions would get "couplings" when picked out of contexts.

This is a bit dodgy in the sense that it seems to put the information in places that it doesn't belong.

So you might argue that if you fetch a function out of a word, then it should get a special state that isn't just "uncoupled", but "anti-coupled", e.g. it won't ever pick up a coupling (until you uncouple it).

Then we might ask how this should behave:

 obj: construct [
     x: 10
     /foo: func [y] [
        helper: does [print [.x + y]]
        helper
    ]
 ]

In order for that to work, the "anti-coupled" state would have to keep climbing the virtual bind chain vs. stopping at the frame for DOES, to use the .x from FOO.

The problems here don't seem insurmountable...but I'm definitely mulling over whether the picking up of couplings should be limited to a different kind of function, e.g. a METHOD. It seems to me like it's good documentation, but it would help avoid contention:

 obj: construct [
     x: 10
     /foo: method [y] [
        helper: does [print [.x + y]]
        helper
    ]
 ]

With a situation like that, it could be the non-method character of DOES which you can use to trust that it won't have a competing meaning for .x

This stuff is all in its early stages, but it really is way more promising than what came before it...

I am pretty sure that CONSTRUCT--not propagating the binding deeply of the object onto everything inside the object--is the better answer for the default of making objects, e.g. {x: x}

The other idea, of making something that spreads its influences deeply, should probably have a name.

CONTEXT wouldn't be a bad name, actually (and has historical precedent as an alias for MAKE OBJECT!), but I'm using that word with another nuance. It's the same functionality as WRAP but just returning the context, so maybe wrap:context could say that you want the context back, not the block?