Closed edemaine closed 1 month ago
I think moving the declaration above the block sounds good. Having it available inside the then
, else
, and following blocks seems to make sense in the case of unless
. It might be a little strange but we can try it and see how it goes.
Thinking about this more: Moving the declaration above the block works well for a simple assignment:
unless x := f()
return
else
console.log 'good', x
process(x)
↓↓↓
const x = f()
if (!x) {
return
} else {
console.log('good', x)
}
process(x)
But it's trickier for pattern matching assignment:
unless [x, {type}] := f()
return
else
console.log 'good', x, type
process x, type
↓↓↓
const ref = f()
const [x, {type}] = ref
if (!(Array.isArray(ref) && ref.length === 2 && typeof ref[1] === "object" && ref[1] != null && "type" in ref[1])) {
return
} else {
console.log('good', x, type)
}
process(x, type)
We can't do the destructuring until after the if
check. Then it's difficult to do it (especially with const
) for both the else
block and after the else
block. Even worse, it doesn't really make to do the destructuring declaration after the else
block, because in the then
case (unless the then
clause has a guaranteed exit) there's no meaning for x
and type
. I see now that #1215 mentions this about Swift guard
s:
- If the condition creates any bindings (such as
guard x? := getX() else { STATEMENTS }
), the bindings are only available outside the else block.STATEMENTS
must be an "exit" of some sort, in order for 1 to be possible.
This is a little more conditional than I had in mind. Here's one possible behavior:
unless
moves to else
block if there is one.else
block and the then
block has an exit according to isExit
, then the declaration moves after the unless
block (as currently implemented).else
block and the then
block might not exit, then the declaration doesn't go anywhere.else
block and the then
block has an exit. In this case, you didn't need an else
block at all, as it executes in the same cases as the block after the else
. Perhaps then the else
block just becomes a braced block (in case it has other declarations) without a leading else
, and we can put the declaration before it.I think this makes some sense: the declaration goes in the place(s) where it makes sense. But wouldn't hurt to get more input on this before I go ahead and implement it.
By the way, until
behaves very differently: there, we always want the declaration after the loop (just like currently implemented). Hmm, but if there's a break
, this won't work... So I guess we need to fail in that case?
I implemented the above plan, and fixed the missing negation of pattern matching mentioned in https://github.com/DanielXMoore/Civet/issues/756#issuecomment-2097279047 This should be ready to go!
So I implemented the idea that declarations of
unless
anduntil
go after the block instead of inside:However, I'm no longer sure this is a good idea. For example, what if we have an
else
block?I'm pretty sure
x
should be bound inside theelse
, and for symmetry with the other situations, I think also afterward? So my temptation would be to put the declaration ofx
above theunless
instead of after it, so that it's accessible everywhere. It doesn't seem particularly bad to expose it to the "then" clause — this is actually more symmetric withif
— what's important is that it's exposed in the "else clause and outside block, because it can be useful there. Whereasif
keeps the protection of the binding to the "then" clause, because that's the only place it is helpful. What do you think?Fixes #1215, fixes #756