Evaluating the worthiness of CELL_FLAG_UNEVALUATED

One question that comes up fairly often with new users is why if [var = value] [...] doesn't do what they expect.

We know that blocks are truthy, and passing a literal block to an IF will not run the code in the block. They should have written if (var = value) [...] or just plain if var = value [...]. But this isn't obvious to a newbie.

When I've encountered this situation myself, it's usually because of changing a WHILE into an IF, or something of that sort. Of course, I know why a WHILE puts a block around its condition and an IF does not...but for a new user that's certainly non-obvious.

Hence, CELL_FLAG_UNEVALUATED was born

I had the idea that values would have a bit on them that would say whether they were the product of an evaluation, or just literal. Hence when IF ran, it could tell the difference between:

block: [a b c]
if block [...]

and

if [a b c] [...]

The former it would allow, the latter it would prohibit with an error. This seemed like a good thing.

Does it do more harm than good?

You probably shouldn't write if [literal block] in your code (unless you were writing a test, or plan on transforming it before execution). Yet you might well want to do this in a composition.

 compose/only [... if (conditional-expression) [...] ...]

If that conditional expression is a literal block, then do you want to get an error on it?

And really, what if someone does write:

 condition: [var = value]
 ...
 if condition body

If they intended that condition to execute, that's a mistake that wouldn't be caught. So here we have the check impeding experts...and not protecting new users as thoroughly as one would hope.

Nearly every safety feature has shown to be not worth it

Because Rebol is subtle and can be difficult to debug, it has been tempting to throw in various safety mechanisms. But nearly all that have been tried have been thrown out, instead addressing the relevant concern through a different angle.

There's no question that the existence of CELL_FLAG_UNEVALUATED and maintaining its state makes the evaluator code more complicated. Yet I've found being able to read it as being helpful in some cases when debugging or deprecating APIs. It's likely good to keep the feature and let functions know this property of their callsites if they find it necessary.

But should things like the protection of IF and other conditional clauses be in the box, or should it be part of an adapted definition in a "training wheels" module...not enshrined in the mainline code? (For that matter, should the website demo use training wheels mode by default, if it existed?)

I think I'm leaning toward making this a training-wheels-only feature, and having the stock IF (CASE/etc.) not error on literal blocks. Of course, the training wheels would have its work cut out for it to emulate every conditional test in a CASE statement, it would basically have to reimplement CASE. So this is a fairly heavy change.

Hm. I think newbies might get tripped up on this temporarily or accidentally, but it doesn't seem like a feature that should be changed because a newbie hasn't yet grasped something as fundamental as literal values.

Another area this has caused problems is in tests. You want to write (<foo> = if [1] [<foo>]) but can't because you're being "protected".

There are definitely some real, useful, power-user applications of the unevaluated flag. This leads me to not want to rip it out entirely. Some functions get real benefit from being able to tell if the callsite was 10 or 5 + 5 and it makes a difference they can action on...helpful in dealing with interface changes, for instance.

But building it into conditional tests in the system itself is probably a mistake. What it's already done is introduce two tests: IS_TRUTHY() and IS_CONDITIONALLY_TRUE() (and by extension, IS_FALSEY() and IS_CONDITIONALLY_FALSE()). Then you have to have explanations that say "well, it disallows literal blocks, which you don't want to do in some places for sure but it's thought to be helpful in some interfaces".

This flag was tried for a while as a solution to mitigate non-block branches, and scrapped there as well. Because non-block branches were figured as a bad tradeoff all told. So I think IS_CONDITIONALLY_TRUE()
and IS_CONDITIONALLY_FALSE() in the core needs to go.

I still want to be thinking about how one might build the training wheels layer and use the bit, so we'll put a pin in that.

A post was merged into an existing topic: Construct For Updating Variable With Value (If It's Not Void)

My feeling is that it can be difficult to predict what newbies will struggle with. As long as the built-in help is available and clearly written, ideally with some kind of feedback mechanism available, I think that would provide a quicker way forward than building stuff for an (as yet) imaginary audience of users. :slight_smile: Of course, if your gut tells you that some guardrails are necessary from the outset, then I trust your judgment. If the protection is easy to implement, I don't think I'd prioritize its inclusion very highly until it becomes a quantifiable compaint.

1 Like

Beyond the sheer nuance and complexity of implementing CELL_FLAG_UNEVALUATED--there's a new twist: Plain blocks now have evaluator behavior. (of adding binding)

This makes the flag useless for its original purpose. So combined with the other reservations, it's getting the axe. We get a fairly negligible performance boost...but it frees up the cell flag for another purpose, which could have high leverage in implementing optimizations.

I think there will be other opportunities to accomplish interesting ways to filter calls. For instance, debuggers need to have access to a copy of the expression as it was written in source. If there's a mode in which these instructions are built as blocks, then you could have extra diagnostics which say whether there's something problematic about that pattern. It wouldn't be fast... but if you're having problems and want to pay for the diagnostic mode, it could be worth it to get any insights it might have.