Hedging on UNLESS

Not everyone has historically been a fan of the idea that UNLESS is a synonym for IF-NOT. It's the same number of characters, and it's been a source of controversy. When people go around changing if-not to unless, some people change it back because they think it made matters less clear.

I'd come up with what I thought was a more interesting usage of UNLESS, as an infix operator. It would evaluate both the left and the right. If the right evaluated to NULL then it would use the value of the left, otherwise the value of the right would override.

>> 0 unless null
== 0

>> 0 unless <a thing>
== <a thing>

I envisioned it being a way of reordering the order you'd read code, where you could put a default first...perhaps wanting it first to emphasize the most common case:

error-id: 0 unless case [
    not block? foo [13]
    closed? connection [29]
    ...
]  ; don't need an ELSE here, the 0 is up-front

But using it as a defaulting mechanism has the problem that the left-hand side is not in a BLOCK!, so you are always running the default case's code. Trying to fix this by requiring the left side to be a block would look ugly, and seem a bit of a nasty "surprise":

thing: [  ; looks like a block literal
   foo baz bar
   ...
   x + 100
] unless switch z [  ; oh wait, that was *code*? :-/
    ...
]

So you'd definitely need to use GROUP!s on the left.

thing: (
   foo baz bar
   ...
   x + 100
) unless switch z [
    ...
]

But the value proposition gets a little bit lost once you're writing too much stuff that it pushes the UNLESS out of sight. You're better off with:

thing: switch z [
    ...
] else [
   foo baz bar
   ...
   x + 100
] 

Not picking a definition yet might be the best idea?

Since I just came up with this alternate idea, I can't tell you how many uses I'd find for it. I'll have to try looking around consciously looking for use cases. Maybe it doesn't come up often enough, and people will miss the old UNLESS enough to be worth bringing it back.

I'll commit the new idea, and we can try it for a while. But the work has already been done to excise UNLESS, and to safely detect old usages and give warnings. There's a native IF-NOT synonym, and it's easy enough for people who want the old definition to say unless: if-not/ if they like.

That looks much cleaner. I'm in that camp that unless is not a good if-not

2 Likes

So far, I'm liking this new definition. It still works for the original defaulting idea, too.

Here's a case from some debugger code:

max-rows: 20 unless limit then [
    if limit = <unlimited> [
        99999  ; as many frames as possible
    ] else [
        if limit < 0 [
            panic ["Invalid limit of frames" frames]
        ]
        limit + 1  ; add one for ellipsis
    ]
]

You can contrast this with "classical Rebol style":

max-rows: any [
     all [
         limit
         either limit = <unlimited> [
             99999 ; as many frames as possible
         ][
             if limit < 0 [
                 panic ["Invalid limit of frames" frames]
             ]
             limit + 1 ; add one for ellipsis
         ]
    ]
    20
]

And you can still write it in such a way if you wanted to. But I think using UNLESS with THEN organizes it in a way that makes more sense here.


In any case, with this adjustment, it's working out well enough that I think it's going to be a keeper...and the IF-NOT synonym is a waste of the word in comparison.

2 Likes

I had hedged on the idea of UNLESS as an infix operation that I came up with from 2018, based on it having a few problems.

A large number of these problems have been resolved in the intervening 6 or so years.

I've gotten less squeamish about taking groups literally (e.g. AND and OR do so). I think it should take its left hand side literally, and a GROUP! should evaluate only if the right hand side isn't true.

There are bad uses:

value: (
    some long expression
    that takes many pages
) unless ...

Reminds me of an old quote something along the lines of "There has not now--nor will there ever be--a programming language invented in which it isn't easy for people to write bad code."

Is Infix UNLESS More Common In English?

unless you want to get fleas, don't lie down with dogs
vs.
don't lie down with dogs unless you want to get fleas

AI don't have any particular agreement or data to support it, but they do seem to agree that at least in spoken English the infix is more common.

X UNLESS CONDITION is Strange...

I thought it was clear that the right hand side is what evaluates to the value to use instead of the left.

>> x: 0 unless any [1 + 2]
== 3

But maybe it's not so obvious?

>> x: 0

>> x: 100 unless 1 < 2
== ???

It almost seems like the UNLESS is controlling the entirety of the evaluation. As if it would leave X at 0. :frowning: That not possible... leaving X at 0 would require having access to X's previous value, and UNLESS can't reach back to get it.

Seems UNLESS could reject ~okay~ antiforms, just to help avoid this particular potential confusion.

1 Like

So I have a new mechanism I'm pretty excited about called VETO.

>> reduce [1 + 2 (void) 100 + 200]
== [3 300]

>> reduce [1 + 2 (veto) 100 + 200]
== ~null~  ; anti

There are far-ranging applications for the definitional "veto" error that the VETO native produces.

But I also have a construct called VETO-IF, which will produce either a veto error, or a void.

>> reduce [1 + 2 (veto-if 10 > 20) 100 + 200]
== [3 30]

>> reduce [1 + 2 (veto-if 10 < 20) 100 + 200]
== ~null~  ; anti

That's a rewrite of what you'd have to say as:

reduce [1 + 2 (if 10 > 20 [veto] else [void]) 100 + 200]

Because IF doesn't return VOID on no branch, but WHEN does, it's perhaps more technically valid to call it VETO-WHEN:

reduce [1 + 2 (when 10 > 20 [veto]) 100 + 200]

reduce [1 + 2 (veto-when 10 > 20) 100 + 200]

And you might ask "why are you even shorthanding that".

But anyway, when thinking it would be called VETO-IF, I did wonder a bit about a complement called VETO-UNLESS.

>> reduce [1 + 2 (veto-unless 10 < 20) 100 + 200]
== [3 30]

>> reduce [1 + 2 (veto-unless 10 > 20) 100 + 200]
== ~null~  ; anti

Just trying to write these examples here makes me confused. The negative logic is weird. It bolsters my belief that the "UNLESS as IF-NOT is not worth it".

2 Likes

I replaced arity-1 IF in PARSE with CONDITIONAL, shortcut COND.

>> parse [a b] ['a cond (1 = 1) 'b]
== 'b

>> parse [a b] ['a cond (1 = 2) 'b]
** Error: PARSE BLOCK! combinator did not match input
** Id: parse-mismatch  

This is a way to wedge boolean logic into a parse rule, since you can't just use NULL or OKAY antiform variables directly as rules (too much potential for accident).

It made me wonder: what could COND(ITIONAL) DO IN REGULAR EVALUATION? It would have to be something that could break the flow.

Which made me think of:

Well, what if this is what COND did?

>> reduce [1 + 2 (cond 10 < 20) 100 + 200]
== [3 30]

>> reduce [1 + 2 (cond 10 > 20) 100 + 200]
== ~null~  ; anti

COND would produce VETO on NULL, and VOID otherwise.

I think I like that.

1 Like