Safety Concept: Error on discarded plain BLOCK! ?

I just had a bug that was rather frustrating to find. I changed:

foo: func [
    {Description}
    param [...]
    /refine
][
    ...
]

into:

foo-core: func [
    {Description}
    param [...]
    /refine
][
    ...
]

foo: adapt 'foo-core [
    {Description}
][
    ...
]

But ADAPT doesn't take two parameters. I should have eliminated the "spec" block from the ADAPT entirely, but I only deleted the parameters. This effectively gave me:

foo: adapt 'foo-core [
    {Description}
]  ; "spec" block treated as the adapt "prelude"

[...]  ; stray random block, thrown out

There's no warning in this case, because a plain BLOCK! evaluates to a plain BLOCK! and gets thrown out.

There's nothing we can do. Or... is there?

This kind of thing almost always represents an error. This got me to wondering about generalized quoting. Might it help us?

What if plain unevaluated BLOCK! was an error in evaluation if it was not a parameter to anything, and not a result of anything...?

So maybe this would be an error:

all [
    a = b
    c = d
    [this block does nothing]
]

But this would not:

data: all [
    a = b
    c = d
    [assignment target, it's okay]
]

And this would also be fine:

foo: func [return: [block!] a b c d] [
    all [
        a = b
        c = d
        [result of function so it gets used]
    ]
]

Maybe quoting could let you subvert the rule:

 all [
    a = b
    c = d
    '[just throw this out, don't complain]
]

Note COMPOSE has power for this kind of thing built-in:

 block: [thing to throw out]
 data: all compose [
    a = b
    c = d
    '(block)  ; the tick would also suppress ACTION! evaluation, etc.
]

I don't know if the QUOTED! exemption is necessary or not, because this could run into problems as well of throwing out a quoted thing.

Challenge: when is non-evaluation utilized?

There's been some play with the fact that things don't evaluate to make constructs that have markup in them. Think of for example a modification of EITHER that lets you label the branches:

 my-either condition [<tag> ...code...] [<tag> ...code...]

Skipping the tag silently might be considered a feature, because the code is looked at. I don't know, maybe having to skip that tag before executing is better practice.

Just asking the philosophical question here of "why is throwing away inert interstitial expressions of great value". We have ELIDE and COMMENT. Might we do more for the sanity of the language if we noticed and errored if values were being silently discarded?

Usually trying to implement a rule like this shows problematic cases, but I tend to write it up as a way of seeing if I can talk myself out of it by coming up with a disproof before bothering to try writing it...

3 Likes

I have used this feature

  • for blocks as a quick way to comment out big chunks of code during debugging, and

  • for strings as an experiment to get "live" comments which could be inspected during runtime.

I am not sure how these usages weigh against finding the class of errors you mention above.

comment [...] and comment {...} cover both uses; as the code remains in the source and would be inert in evaluative contexts. Though not as "quick". I'd proposed ** as being a "comment-to-end-of-line" operator at one time, which I haven't really pushed on...but for quickness, it would help.

I'd suggest that if you are doing some kind of debug session then making:

!!debug-foo: :comment

Or something more visible might be good to use. Because you could have that at the top of the file. Then everywhere you were debugging something relative to a FOO problem, you would notice that you'd left the commenting-out in.

1 Like

And yet again I get bit by this...

I was adding functionality to make UPARSE able to work on PATH!, TUPLE!, and URL!.

Here was what I wrote:

case [
    any-sequence? input [input: as block! input]
    url? input [input: as text! input]
]

Seems simple enough, but I had perplexing problems that took way too long to sort out.

Turns out it's because UPARSE has a /CASE refinement (like PARSE)

So CASE was simply either NULL or a # meant it was evaluated and discarded, and that block never evaluated.

I'm sure having a stepwise debugger would help. But still, I wonder if there's something we could do. A customizable evaluator hook, where you can pick if you want warnings on this? I dunno.

1 Like