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/~

Replacing GET-WORD! for Polymorphic “GET:ANY”

So there's a lot of things in the mix, now... most importantly ^META is now the go-to for "give me that, as it is", without execution... and without decaying unstable antiforms.

This all ties into Lift the Universe, a concept from which there is no going back.

So what :FOO will do is completely open. I've suggested it might mean TRY when associated with PICK-ing:

>> block: [a b c]

>> block.4
** Error: there's nothing there, man

>> :block.4
== \~null~\  ; antiform

I think this idea has some merit.

So let me hammer out a couple of thoughts:

It's not really that great an idea to have values that are either an ACTION! or NULL. The problem there is that if you just try to call the action through a word reference...and it's null... then the null will silently just not run anything and be a no-op.

As a sample consequence of this principle, I don't think null tolerance of a slashed assignment is a good idea, nor does it need a special notation:

>> /foo: func [...] [...]
== ... ; an action

>> /foo: 10
** PANIC: Can't use /FOO assignment to assign an INTEGER!

>> /foo: null
** PANIC: (This should also panic)

If you really have something that may be an action, or may be null, then ^META-assign it via ^foo: and you won't get complaints.

Assigning TRASH! is less dangerous, because that is more like assigning a function that errors when you call it. But there is no operation for turning nulls into trash...

Unsetness is also less dangerous. And assigning VOID now "unsets" variables when done through non-^META assignment, which is easier to produce from null via an OPT.

It seems to me that allowing /FOO: to take void on the right hand side to unset variables is safe, and unlikely to happen on accident since it's hard to make voids on accident. This gives you a state you can test for (unset? $foo). So right hand side should be either a void or an action.

Propagating that gets tricky, e.g. if you want /bar: foo/ which is willing to propagate an unset state. foo/ on an unset variable seems obviously to need to be an error. GET can't get an unset variable (it's deliberately out-of-band of representable non-dual states, e.g. less-than-trash). You could say:

/bar: opt if set? $foo [foo/]

/bar: when set? $foo [foo/]  ; WHEN is the void-when-untaken IF

Pursuant to the "leading colon is TRY" syntax, maybe this could be:

/bar: opt :foo/

This would require a concept that accessing an unset variable isn't an immediate panic, but a definitional error... which offhand I'm not sure is that great an idea. But how different is an unset variable from trying to pick an item out of a block that's not there?

It could be that this modality of PICK or GET is a special mode, like pick:relax and unrelated to TRY, so it's not definitional-error related. That could solve the conflation.

Another point of concern: terminal slash as distorting...

When thinking about interplay with an action in a PACK! as the "unsurprising" bit. (If that's going to be viable, which currently I don't know, I just know it's the last thing I'm going to try.)

One of those things is whether foo/ is distorting, e.g. a variable holding a plain ACTION! should give back an action in a PACK! in that world. This would allow you to write:

>> bar: foo/
== \~[~&[frame! ...]~]~\  ; antiform (pack!)

If you didn't have that, you would have to write:

>> /bar: foo/
== \~&[frame! ...]~\  ; antiform (action!)

The thing is, if you wanted the ACTION! as-is, you could just say ^foo.

I've made it clear that the "PACK! acting as unsurprising bit" concept is speculative, but just wanted to mention that it comes into play here.

This is no longer speculative. It's working far better than I could have hoped.

Yes... this is the correct behavior.

I don't think "leading colon is TRY".

I think :xxx.yyy means: "if ^xxx.yyy would have returned a VOID!, return a NULL instead."

So if we think this way, it would mean:

>> block: [a b c]

>> block.4
** PANIC: ...

>> :block.4
== \~null~\  ; antiform (keyword!)

>> ^block.4
== \~,~\  ; antiform (void!)

>> append [q r s] ^block.4
== [q r s]

This seems satisfying to me... that it's not an ERROR! to pick outside the range of a block... there's just nothing there to pick. So it panics if you use a non-^META access, gives you a void if you use meta access, and null if you use leading colon access...