ASSERT vs. [end]able FAIL

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 switch ... [...] else [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.

1 Like

This five-year-old idea has been put to good use and feels like it has proven its mettle.

But reviewing old posts I realized that it does theoretically suffer from the line continuation arity bug.

This is to say you might have some code like:

 blah blah blah
 more blah blah

And decide you're going to just throw a FAIL in the middle:

 blah blah blah
 fail
 more blah blah

Because you're used to the idea that FAIL can be used with no arguments. Yet the more blah blah will be picked up as an argument to fail, as if you'd written:

 blah blah blah
 fail (more blah blah)

The consequences aren't quite as dire as when this was being used with the likes of RETURN...because FAIL generally stops execution anyway. But it could lead to unexpected results.

I just wanted to reiterate that <end>-able constructs...cool as they seem...have this blind spot when code is broken across lines.