ASSERT vs. [end]able FAIL


#1

To concretize this discussion point, let’s use a simple example. We have some C functions with names like “RL_rebRun” that we want to extract “rebRun” from. We could say:

parse cname ["RL_" copy rebName to end]

After this rebName will either be whatever it was beforehand (if it didn’t start with “RL_”), or it could be something like “rebRun”. How might we verify it worked?

What about ASSERT?

PARSE now returns null (not false) on failure, so it can participate in the NULL protocols (e.g. be used with things like ELSE). But null is still falsey, so it can be used with ASSERT:

assert [parse cname ["RL_" copy rebName to end]]

That doesn’t look too terrible, though it is a bit “disruptive”. The assert has taken the center stage as the first word you see, as opposed to having the failure handling be more of an exception/afterthought.

But here’s another thing you might not know. The implementation of ASSERT is a no-op in Ren-C:

assert: func [
    {Ensure conditions are conditionally true if hooked by debugging}
    return: <void>
    conditions [block!]
        {Block of conditions to evaluate and test for logical truth}
][
]

This is because ASSERT has no default implementation, but can be HIJACKed by a debug mode with a custom validation or output routine.

As it so happens we don’t have “debug mode” yet. So it just gets HIJACKed during boot. But the point is that it’s designed so that asserts could be no-ops in a “release” mode…which wouldn’t run the code at all. In our case, that means no PARSE, and rebName is never set.

How about an ELSE with a FAIL?

parse cname ["RL_" copy rebName to end] else [
    fail "The name didn't start with RL_"
]

That works, but now we’re on the hook for writing some kind of error message.

Or… are we? What if we just wrote:

parse cname ["RL_" copy rebName to end] else [fail]

If you think about it, a FAIL with no arguments doesn’t have a lot of options for what to do besides report an error. So why not make it <end>-able (the same way HELP can accept either no arguments or one argument), and assume that a FAIL with no arguments is just a way of saying “fail here”?

There are other applications for this:

switch type of x [
    integer! [...]
    text! [...]
    fail
]

That’s more succinct than default [fail]. And maybe it’s not right for everyone, but it’s interesting in a sort of “you get what you pay for” failure message. If it happens often and you think it deserves a more informative message than just showing you a place in the code where it happened, you can expand on it.

I think I like it. In fact, thinking about it just got me to realize that we could make any RETURN willing to take no argument to act as return void would, and make things simpler and more flexible. So why not have FAIL go the same route?