Replacing GET-WORD! for Polymorphic "GET:ANY"

So the Big Alien Proposal has been settling out, and I've backed off the idea that you need a leading slash to assign actions to words.

An area that still hasn't been pushed through is what to do about replacing the former :FOO behavior for what was "GET-WORD!" (and is now a "refinement").

Rebol2 used :FOO to suppress function invocations, and fetch any other variable normally. But it didn't let you get unset variables:

rebol2>> type? :append
== function!

rebol2>> num: 10

rebol2>> type? :num
== integer!

rebol2>> type? :asdfasdf
** Script Error: asdfasdf has no value

R3-Alpha made :FOO a fully generic GET, that could also get UNSET!

r3-alpha>> type? :append
== action!

r3-alpha>> num: 10

r3-alpha>> type? :num
== integer!

r3-alpha>> type? :asdfasdf
== unset!

Red followed suit, and does the same thing.

I never liked this idea of coalescing the two things. If you meant to suppress an action invocation, conflating that with "but if it's unset that's okay" has been a recipe for disaster.

In fact, the Big Alien Proposal moves in a stronger direction... trailing slash doesn't just suppress action invocation, it ensures that the thing is an action:

>> type of append/
== ~{action!}~  ; anti

>> num: 10

>> num/
** Error: trailing slash in paths can only fetch ACTION!, not INTEGER!

>> asdfasdf/
** Error: asdfasdf is NOTHING

But What If You Want Other Behaviors?

I've floated the idea that if you want to tolerate getting something that may be unassigned, you can do that with a trailing ~.

>> foo: ~
>> bar: 1020

>> foo
** Error: foo is NOTHING

>> foo.~
== ~  ; anti

>> foo/~
== ~  ; anti

>> bar.~
== 1020

>> bar/~
** Error: trailing slash in paths can only fetch ACTION!, not INTEGER!

>> type of append/~
== ~{action!}~  ; anti

That seems pretty slick.

There is one qualm, which is that bar.~ can theoretically be meaningful, if you're picking something like a quasiform out of a MAP!, because quasi-blank is a valid map key. Typically if you put something in a TUPLE! like that which can be literally looked up, it is looked up literally.

We could go the other way with it, and put it at the beginning...

>> ~.foo
== ~  ; anti

>> ~.bar
== 1020

But if you've got a long chain of things, it puts the "I'm okay with this being unset" far away from the thing that's actually unset.

>> ~.obj1.obj2.field
== ~  ; anti

>> obj1.obj2.field.~
== ~  ; anti

So comparing there, it's FIELD that's the thing that you're "modifying" the access of.

But...I suppose looked at that way, you might put it anywhere:

>> obj1.obj2.~.field
== ~  ; anti

Well, that's a bit interesting, in the sense that this way you could cut short any chain of accesses:

>> ~.obj1.~.obj2.~.field
== ~  ; anti

That might translate to "Suppress an error and just give me NOTHING if any of OBJ1, OBJ2, or FIELD are unset" (or maybe just any kind of unavailable?)

Well actually you can do this either way, with a before or after convention:

>> obj1.~.obj2.~.field.~
== ~  ; anti

One thing to like about the after convention is that it wouldn't produce things that look like unix paths, e.g. ~/foo looks like a file hanging off your home folder.

After Convention Looks Good, Still Has A Hole...

There's still one hole here.

foo.~ gives you a non-antiform-action value that may be an unset variable (or tripwire, we'll assume they're both covered)

foo/~ gives you either an antiform action value or the value of an unset variable, but errors on other things.

(I should point out that in foo.bar.baz/~, that is structurally like (foo.bar.baz)/~ so there would be no putting it in the middle as I suggested you might be able to do with the tuple version. But appropriately, that wouldn't make any sense for an antiform action to be anywhere but at the end, because you can't "pick out of it" the way you may be able to do with a tuple product.)

Anyway... the hole is the case of where you genuinely want anything. How to get the behavior of R3-Alpha and Red's GET-WORD! ?

This is actually pretty rare. If you've assigned something to a variable and you have no idea whether it's a function or a plain value, you don't know how to use it. Unless the function is arity-1 and promises that it's pure (otherwise you'll have trouble with treating it with value semantics).

Could do like Rebol2 and punt on it... just say you have to use get:any and move on. :face_with_diagonal_mouth:

Or expand foo.~ to say antiform actions are acceptable to access with that. :frowning:

Another Hole: What About ACTION! NULL-tolerance?

This is something that comes up...which we might write up like this:

>> foo: null
>> bar: func [x] [...]

>> proxy-foo: foo/~null~
== ~null~  ; anti

>> proxy-bar: bar/~null~
== ~#[frame! ...]~

Uuuugly. :goblin: But it's more often the case you want to fetch action-or-null than it is that you want to fetch action-or-not-set.

But Wait... since we know it's not an invocation, TRY could be used!

>> proxy-foo: try foo/
== ~null~

>> proxy-bar: try bar/
== ~#[frame! ...]~

The convention could be that trailing slash does a RAISE, but only if the variable is NULL... otherwise it does a FAIL. So TRY could effectively pipe the NULL!

That's subtle, but I feel the subtlety of "actions pass, nulls raise, everything else fails" is as-easy or easier to explain than any attempt to do this with a notation.

Sidenote, What About Protecting Assignments?

I've become very accustomed to being able to unset variables on the fly:

x: y: z: ~

mode: ~<mode not initialized by INIT-SYSTEM>~

Though we could maybe draw a distinction with SET-BLOCK...

>> [x]: ~
** Error: SET-BLOCK! variables must be var.~ to unset

>> [x.~]: ~
== ~  ; anti

Eeeeh. No.

Anyway, There's Enough Here To Get Started

I've converted most everywhere in the code that meant to get an action to FOO/ But there's still a fair number of places in the code still using :FOO. I'll have to start surveying them, but my current leaning is to go for it with the trailing tilde idea.

Sigh, no that can't work.

Because TRY already takes advantage of the "I know I'm not executing" state to suppress TUPLE! picking failures:

>> block: [a b]

>> block.3
** Error... (raised)

>> try block.3
== ~null~  ; anti

So what happens when you mix a tuple and a terminal slash? The answer is conflated:

>> try foo.bar/
== ~null~  ; anti

The higher priority feature is the tuple selection, it wins.

Priorities, priorities. :neutral_face:

Which reminds me of the idea of foo. That's a TUPLE! and it could mean "RAISE an error, but don't fail, if FOO is unset". So try foo. would give you null if foo was unset (try foo has to consider the case that FOO is a function, and the TRY has to be processing the result of that if so).

Maybe these could be combined... such that foo./ fuses as "get if it's anything but not set", and foo./~ means "get absolutely anything, set or not". Given how rarely this comes up, the fact that it looks weird might not matter.

Or...

I don't love this direction, but in practice "get me anything" is the most common intent.

It may be wiser if that's what foo/~ means (to draw attention to "this could be an action")... and then foo.~ can mean "get me anything so long as it isn't an action". Any filtering beyond that you do with ENSURE, etc.

The answer here may just be "tough luck". Not everything needs to be solved with a notation.

proxy-foo: ensure [~null~ action!] foo/~