Why @ Isn't A Precise Synonym For THE

The SIGIL! @ has non-overrideable behavior in the evaluator. It acts like THE (Ren-C's parallel to Redbol's QUOTE)

>> @ foo
== foo

>> @ (1 + 2)
== (1 + 2)

>> the foo
== foo

>> the (1 + 2)
== (1 + 2)

But it does something a normal function can't...which is to effectively put antiforms in suspended animation in the API.

If it worked in the main language, it would be like this:

>> eval impossible-compose [@ ('~null~)]
== ~null~

>> eval impossible-compose [@ (null)]
== ~null~  ; anti

You might imagine the @ operator doing a little cheat, by turning quasiform null into antiform null. Then IMPOSSIBLE-COMPOSE could just do this:

>> block: impossible-compose [@ (null)]
== [@ ~null~]

>> eval block
== ~null~  ; anti

For a time this is exactly what it did. But then...how would you literally get a quasiform null? It can't do both.

Today, using some magic in the evaluator, this API code works:

Value* result1 = rebValue("eval [@", nullptr, "]);
assert(result1 == nullptr);

Value* result2 = rebValue("eval [@ ~null~"]);
rebElide("assert ['~null~ = first [", result2, "]]");

It depends on @ not being overrideable and always using the special evaluator magic that lets this work, when you can't do it inside Rebol.

Why Let The API Do Something The Language Can't?

Because quite simply... rebQ(thing) in the API is uglier than 'thing which you can do if you're in the language.

And which would you rather read?

Value* result1 = rebValue("eval [", rebQ(nullptr), "]);

Value* result1 = rebValue("eval [@", nullptr, "]);

The second is also faster.

Anyway, there are other ways of doing it that don't use a literalizing operator. But here, a literalizing operator is what we generally want. We just want it to reconstruct antiforms, and a hack lets us do it.

Is This Dangerous?

The hack has the slightly undesirable property that if you asked for the API code you are running as a block, you couldn't get a legitimate block out of it.

So if you were stepping in a debugger or something, there's an impossible situation in play... something that's not a legal array element is sitting in a slot where the antiform is. And when the evaluator hits that thing it will almost always give an API error saying "you can't do that".

So this would fail:

rebElide("block: [@", nullptr, "]")

There's no evaluation going on inside that block here, so no exception. You can't put antiforms in blocks and receive that block as a normal value.

But as long as it is in an evaluative context immediately after @, then the action is to reconstitute the antiform as an evaluative product.

A debugger would have to have special awareness of this if it were to show you the array, and never let it leak out from the API feed. If you copied it out you would have to get a warning that the antiforms had all been converted to quasiforms and the code wouldn't work.

My belief is that this is worth it. I couldn't justify it (or even figure out how it would be done) when @ was just running an ordinary function, because it would create a crazy parameter convention exception, and leak the mechanic to places it should never be seen. But this isolates it to where it's a built-in evaluator capability that you mostly don't need to know how it works.

1 Like

Having noticed that @ will bind what it gets, and that apostrophe is the operator for getting things not bound, I hope binding is what is usually desired.

Because apostrophe is not going to work very well here.

Value* result1 = rebValue("eval ['", nullptr, "]);

Maybe a good syntax highlighter could help you with that. :-/

Or...I guess you could throw a space in there to make it clearer:

Value* result1 = rebValue("eval [' ", nullptr, "]);

For whatever it is worth, @ has been binding in Pure Virtual Binding II and all the API code is working. In fact it had to bind for anything to work, when I was wondering if it should or not.

So this hopefully shouldn't be a problem.

1 Like