Line Continuation and Arity Bugs: Thoughts?

At one point in time, there was no way to pass something to RETURN that represented a VOID. Because voids completely vanished. If you had a function that took an argument--and a void thing came after it--the evaluator would just keep retriggering until it found a value or hit the end.

This led to the only way of being able to return a void to be to have truly nothing after it. So RETURN became effectively arity-0 or arity-1. If you passed it no argument, it would consider itself VOID. It even had the nice property of being able to chain functions that themselves might be void.

Given that RETURN was doing this, some other functions followed suit. QUIT could take an argument or not. CONTINUE could as well.

But I Just Got Bit By a Variadic QUIT Bug

Without thinking about it, I tried to end some code prematurely by injecting a quit:

 some stuff I wanted to run

 quit  ; added this

 some stuff I wanted to avoid running

And that QUIT ran the stuff I didn't want to run anyway, because it was variadic.

My Kneejerk Reaction Was To Kill The Variadicness

The original case of RETURN has changed, because so-called "non-interstitial invisibility" is dead. You can only make expressions vanish in their totality--not when used as arguments. Doing otherwise caused more harm than good.

Hence return ghost is a perfectly fine thing to write.

I'd been thinking that argument-less RETURN would thus go back to returning TRASH (the default meaningless value). But maybe we shouldn't support argument-less RETURN at all.

But Variadics Can Be Neat

I guess RETURN could always take an argument, and we go back to CONTINUE:WITH and QUIT:WITH.

But those are uglier.

We might question the behavior of the system just randomly slurping up arguments from enusing lines? Especially when APPLY has such a convenient notation now, of some-func // [...]

From a usability perspective, there's certainly a lot of potential for error in getting the arity wrong. Having it be more strict could catch bugs, and make it more likely that variadic arity is being used correctly.

1 Like

I realized this issue is very similar to problems that come up in JavaScript with "automatic semicolon insertion":

JavaScript/Automatic semicolon insertion - Wikibooks, open books for an open world

JavaScript doesn't require semicolons at the ends of lines. There's some debate on whether to take advantage of this--but I'm one of the people who believes it makes the code cleaner. But cases like RETURN have a parallel problem in JavaScript.

So JavaScript's return is variadic, but the automatic semicolon would bias it so that if you have some stuff on a line after a return it gets ignored.

The complexity of the situation makes me feel like erring on the side of fixed arity. If you want to return trash, say return trash. And if you want to return void, say return void. It's more explicit, and I think the "naked" returns probably do more harm than good.

I don't think people will use passing arguments to continue too often, but continue:with value isn't terrible

2 Likes

A design loophole of this is that nulls at refinement callsites (currently) represent refinement revocation.

e.g. if you are to say:

 >> count: null
 == ~null~  ; anti

 >> append:dup [a b c] [d e] count
 == [a b c [d e]]

Then it's as if the :DUP weren't present at all. That seems sensible enough...

...but it would imply that (continue:with null) is a synonym for (continue).

Yet the :WITH is supposed to be a fancy way of saying "pretend I completed the loop body :WITH this value". To pick one example: you're not allowed to have the body of a MAP-EACH end in null:

>> map-each x [1] [null]
** Error: Cannot append ~null~ isotope to collected MAP-EACH block

In practice we want CONTINUE with no :WITH to act as if the loop ended in a void--not as if it ended in a null.

For that outcome in the world of today, that suggests not erroring when the :WITH is null:

>> map-each x [1 2 3] [
       if x = 1 [continue]
       if x = 2 [continue:with null]  ; must be same as plain continue? :-/
       if x = 3 [continue:with void]  ; seems more synonymous w/plain continue
   ]
== []

It's a bit unsatisfying to have that :WITH of a null not acting like if the loop body ended in null and erroring.

This makes me wonder if refinements should implement some kind of rule that makes null and void synonymous... or coupled in a way reminiscent of void-in-null-out.

Perhaps even at callsites, the null state would be illegal:

 >> count: null
 == ~null~  ; anti

 >> append:dup [a b c] [d e] count
 ** Error: Refinements at callsite can't be null, use e.g. MAYBE to get void

 >> maybe count
 == ~[]~  ; anti (void)

 >> append:dup [a b c] [d e] maybe count
 == [a b c [d e]]

What it would mean would be you'd remove refinements from the interface using voids and not nulls. It's a decision kind of in line with how removing elements from MAP! is now done with voids.

So refinement revocation is going to be under scrutiny for a while as I look into this.

1 Like

Now that lone apostrophe (or multi-apostrophe) are no longer legal, I proposed they might step in for the coveted "line continuation signal"

reverse copy imagine-writing your-long-line-here append [a b c] 
  ' [d e f]

I suggested that continued lines could perhaps even transcode as the line minus the continuation:

>> load --[reverse copy imagine-writing your-long-line-here append [a b c] 
     ' [d e f]]--
== reverse copy imagine-writing your-long-line-here append [a b c] [d e f]

This gave me the teeny inkling of a thought that perhaps the evaluator would not evaluate across line boundaries.

e.g. you would be required to use this, and so this would be illegal:

reverse copy imagine-writing your-long-line-here append [a b c] 
    [d e f]

Not illegal to LOAD, but illegal to evaluate. The APPEND would reject going to a new line to gather it's arguments.

You'd have alternatives, like using a GROUP! with internal newlines:

reverse copy imagine-writing your-long-line-here append [a b c] (
    [d e f]
)

reverse copy imagine-writing your-long-line-here append [a b c] (
    [d e f])

It seems like this might prevent errors...

Required or not, I'm starting to feel fairly strongly that the apostrophe to continue lines is a good, light choice for calling out continued lines. So I'm leaning towards going ahead and implementing it.