Beta/One Quoting in the API decision: rebValue() and rebValueQ()

In the question of whether or not APIs should quote spliced values, the answer is...

Only if you ask it to ...but... make it really easy to ask.

Previously this was considered to be undesirable:

const char *spelling = rebSpell(rebQ(word));

To mitigate that, instead go ahead and offer a "Q" version of every variadic evaluating API:

const char *spelling = rebSpellQ(word)

You can choose either way, and you can also switch back and forth with rebQ() to add a quoting level to your splices and rebU() to remove one.

Looks good enough AND makes people feel in control

People may grumble when rebValue("append [copy reduce]", word); acts like:

do compose [append [copy reduce] (word)]

They may have forgotten they were in C (or JavaScript, or whatever), and at runtime in that language the variable names are gone. There is no "word"...just as "word" is gone after the COMPOSE is finished.

Though they may least they'd understand. On the other hand, they might get pretty mad if they get ['10 '20] out of the following if x and y are plain INTEGER!

REBVAL *coordinate = rebValue("reduce [", x, y, "]");

This is why I think the Q is important to have somewhere . If they'd used rebValueQ(), they feel more like "oh, I asked for that...didn't I!"

So the way I look at it is that if someone studies the issues and decides they want to use the Q APIs by default and never touch the non-Q ones, then that's their business. If someone goes the other way, they can do that. But I think looking at what kind of code you're writing and choosing appropriately makes the most sense...which was the direction I'd been headed in.

COMPOSE may be able to benefit from this, too

COMPOSE can use .QUOTE (today, /QUOTE) as a "predicate" to tell it to quote the things it put in slots:

 >> word: first [print]
 >> compose .quote [append [copy reduce] (word)]
 == [append [copy reduce] 'print]

Though it's pretty lightweight as is to say '(...) on your slots instead of just (...), you still might want a specialization for this if you had a lot of them. Maybe called composeQ to line up with the API (I've wondered about doing mixed-case names like that, as I'm not crazy about compose-q or composeq or qcompose.)

In any case, working with compose lately got me seeing it through the lens of "how much better do you expect a C API to a language to be than the language itself." I think it's gotten to right about where it can be, and at this point there's more value in having the API stable than in tweaking it more. So this is what I'm ready to commit to for Beta/One.

Something I mentioned in chat is that a feature that put this decision over the top for me was soft-quoted-branching.

Quick refresher: that's the feature which means you get this behavior:

>> if true [1 + 2]
== 3

>> if true '[1 + 2]
== [1 + 2]

This means that within the framework of the language, you understand that branches of conditionals are one of those things you understand to be quoted by default. If they weren't, they wouldn't be able to see the quote... it would be stripped off when they received their evaluative parameter.

I like this feature for many reasons. It makes EITHER and IF...ELSE fully interchangeable, and not only can your code be shorter, but it can be more performant in CPU and GC/memory usage. I mentioned in chat that where you used to have to write:

 compose/deep/only [if condition [(block)]]

You can get this with the updated COMPOSE splicing rule simply by saying:

 compose [if condition '(block)]

So far, I think the benefits more than outweigh any drawbacks. But consider what you expect now when you say:

rebElide("if condition", some_block);

I don't know about you, but I expect that to run the block as code. My eyes don't pick up any quoting going on here. So if the API were doing it automatically based on some expectation that every slot will be evaluative, that's going to be wrong. It would seem like a bug.

But what if I wrote either of:

rebElideQ("if condition", some_block);
rebElide("if condition", rebQ(some_block));

If I wound up with the block as-is in this case, it would feel like a feature. I'm in control of where the quotes are showing up, so when the language construct acts like there's a quote there I'm not surprised.

So if anyone is unhappy that rebValue("first", some_group) runs the GROUP! as if it had been written there literally...they can blame soft quoted branches for it. They may decide to become a Q-ist, and always use rebValueQ, rebSpellQ, etc. "just to be safe". Until they find out it isn't really safe in any general sense, and then perhaps they'll do what I'm use the right mix of operators for the job, based on what kind of code you're executing.

The big picture thought here was right, but the way I proposed to do it (rebValue() vs rebValueQ()) was wrong.

My new idea is that we take advantage of the new behavior of @ in order to get back a value as-is, and have this be part of the scanned string portion:

So instead of writing:

if (rebDid("action?", rebQ(var))) {...}  ; would run ACTION! without rebQ()

We can say:

if (rebDid("action? @", var)) {...}

When we learn in general that @ x produces x, we take for granted that this is a general language facility.

You could use THE as well:

if (rebDid("action? the", var)) {...}

But I like how the @ stands out as far as these APIs are concerned. It has the right properties.

Nothing is getting "quoted" here. It's just unevaluated. So it won't be the right choice in all cases (like if you want to actually put a tick mark on something to get a QUOTED! value, e.g. to make a soft-quoted branch or similar). And if your intention is to structurally make a single item in a block, you won't want the independent @ as a second value.

Yet for what "rebValueQ" and "rebDidQ" and "rebElideQ" (etc.) were being used to solve, this is the better answer.

This makes a significant difference in the size and perception of complexity of the C library header file...because there were already inline / non-inline / c++ / c89 variants... and when each has a Q or non-Q that doubles the size.

(I've already mentioned the very important point that you want to avoid taxes on people like "did I pick the right API to use, the Q version or not...removing that concern and variance lifts a big weight off one's shoulders.)

1 Like