Name for prefix forms of ELSE and THEN... EITHER-TEST-XXX?


#1

With the recent solution to “ELSE’s dark corner”, it becomes more tempting than ever to use ELSE a lot. But currently, it’s an enfixed userspace function. That makes it slow-ish. But if it were an enfixed native, odds are that switch […] else […] could actually be faster than switch/default […] […], because the lookahead driving enfix is a sunk cost, while path dispatch adds overhead.

But as recently pointed out, there’s no such thing as an enfix function, hence there is no such thing as an enfix native function. You have to take an ordinary native and assign it to a word via enfixing. This is done during boot code for things like + with ADD or * with MULTIPLY.

So the natives behind ELSE and THEN…which are prefix by default…need a name. We could be completely non-creative, e.g. ELSE-NATIVE and THEN-NATIVE:

 >> else-native () [print "This runs" | 10]
 This runs
 == 10

 >> else-native _ [print "This won't run" | 10]
 == _

 >> else-native (if true [print "This runs" | 20]) [print "This won't run" | 10]
 This runs
 == 20

 >> then-native () [print "This won't run" | 10]
 ;-- no result

 >> then-native _ [print "This runs" | 10]
 This runs
 == 10

 >> then-native (if true [print "This runs" | 20]) [print "This runs too" | 10]
 This runs
 This runs too
 == 10

My best idea so far is to consider these as specializations of a ternary operator called “EITHER-TEST”, which would generically work like this:

>> either-test :even? 4 [print "it wasn't even!"]
4

>> either-test :even? 3 [print "it wasn't even!"]
it wasn't even!

Under this logic, ELSE is enfixed EITHER-TEST specialized with ANY-VALUE? as the test, and THEN is enfixed EITHER-TEST specialized with VOID? as the test. To give it a boost it’s easy enough to hand-code individual natives for EITHER-TEST-VALUE and EITHER-TEST-VOID, as we’d like ELSE and THEN to be fast as possible.

So what do people think? Is there a better name for what EITHER-TEST does?


#2

I notice that another function this looks like is an arity-3 MAYBE, which adds a branch…

>> maybe integer! 10
== 10

>> maybe integer! "not an integer"
== _

>> maybe [integer! string!] "it's one of those"
== "it's one of those"

In fact, the parameter ordering looks about right that you could call it MAYBE/ELSE.

>> maybe/else :even? 4 [print "it wasn't even!"]
== 4

>> maybe/else :even? 3 [print "it wasn't even!"]
it wasn't even!
== _

This seems more natural. Then the hand-coded native specializations could be MAYBE-VALUE-ELSE and MAYBE-VOID-ELSE, which isn’t too bad.

It cuts across one of the questions of MAYBE, which was whether or not it should allow you to pass in voids. e.g. is maybe :void? () legal.

Originally I said it was not. I was very concerned about people writing value: false | … | if x: maybe logic! value [print “It’s a logic!”], and mistakenly thinking it wasn’t a logic, by misinterpreting the fact that it passed the test. (similar for maybe blank! value [print “It’s a blank”]). So my “clever trick” was to have these cases return void. This would mean they wouldn’t cause failure in an ANY or ALL, but wouldn’t let you accidentally use them in a condition.

That kind of trickery won’t do for the purposes of THEN or ELSE. voids have to be legal input for the tested value, and if the value passes the test it can’t be mucked with.

So it seems what we’re looking at is that MAYBE* is the unchecked version that returns void on failure, where MAYBE chains into TO-VALUE, and THEN or ELSE are specializations of MAYBE* with :ANY-VALUE? and :VOID? with /ELSE as TRUE. Unfortunately, the ability to specialize a refinement and not its refinement arguments (fully) is not there yet. :frowning:

(Note: It’s on the list, it’s just technically tricky and hasn’t been done. Just doing SPECIALIZE with a refinement as TRUE won’t work because there’s no way to state the ordering. The way you’d express it would have to be adp: :append/dup/part or apd: :append/part/dup… because the parameter orderings would be different for the two of these cases.)

But just because you can’t do it today doesn’t mean you won’t ever be able to do it. So this seems like it might be better to model this as “MAYBE plus BRANCH” as opposed to “EITHER plus weird notion of applying condition but returning value…” The latter is more confusing.


#3

(It says I’m reviving this thread from 11 months ago. I remember exactly where I was when I wrote it. I guess it’s been about a year since then…interesting.)

Times have changed, and EITHER-TEST-VALUE is the current formal name for prefix ELSE, and EITHER-TEST-NULL is ALSO (not THEN, which has become infix IF).

This name conveys their relationship to the function they specialize…either-test.

10 = either-test integer! 10 [
    print "this won't print"
    <result>
]

<result> = either-test integer! #issue [
    print "this will print"
    <result>
]

So the EITHER-TEST-VALUE and EITHER-TEST-NULL are hand-optimized specializations of that, which function identically to specializing EITHER-TEST with the VALUE? function and the NULL? function respectively.

While that may appear nice and tidy, the name is a mouthful for anyone wishing to use it. And we’ve seen that there are situations where the complete-the-left-hand-side for the enfix form is not ideal:

 append data switch [...] else [...]

… gets interpreted as…

(append data switch [...]) else [...]

And since APPEND always returns a result, the ELSE branch never run. This causes people to need to use the <- operator or ():

append data <- switch [...] else [...]
append data (switch [...] else [...])
append data (switch [... (...)]) ;-- with constructs supporting "fallout"

But if you used the prefix form of ELSE, e.g. EITHER-TEST-VALUE, you could just say:

append data either-test-value switch [...] [...]

Which looks horrible. So can anyone think of a name which they could imagine actually using in such a circumstance?

It may not be a problem worth solving, the arrow (and SWITCH+CASE fallout) are kind of the solutions that were worked up. But I thought I’d put it out there.


#4

The dialect which MATCH uses is comfortably settling on being known as “the MATCH dialect”. (It will likely end up being the same format as function parameter specification blocks!) So at minimum, EITHER-TEST needs to be renamed to EITHER-MATCH.

But all things being equal, it’s much nicer if you can use MATCH with ELSE. Compare:

 EITHER-MATCH [integer! whatever!] value [code if no match]
 MATCH [integer! whatever!] value ELSE [code if no match]

But if you’re going to match NULLs and not have to get too tricky to do it, you’re going to need a form like the first.

 if match [<opt> integer!] null [
     print "If we 'matched' a null and passed it through, this won't run!"
 ]

This actually falls under the default behavior for MATCH is to voidify any falsey thing (I’ve written about this). I think it’s too easy to make a mistake with it.

So we need a way around all that, and EITHER-MATCH is one way.

We could also have a refinement to MATCH that took a block, so it was MATCH/ELSE.

 EITHER-MATCH [integer! whatever!] value [code if no match]
 MATCH/ELSE [integer! whatever!] value [code if no match]

Back in the day–rightly or wrongly–people despised IF/ELSE. I don’t know if that points to trying to do this with a refinement being a terrible thing.

A strange possibility I brought up was to work around these situations with quoting. Make your spe block have a quote level, then quote the value you’re testing. Then check for null, then unquote:

 UNQUOTE (MATCH '[<opt> integer!] QUOTE value ELSE [code if no match])

I think that’s very interesting, but this situation comes up often enough that something like EITHER-MATCH or MATCH/ELSE are needed.


As for prefix THEN and ELSE, hrrrm. I’m still at a loss. Prefix-THEN looks at the first item for nullness. If it’s null it returns that, and if it’s not null it runs the block of code after it.

>> pre-t 10 [1 + 2]
== 3

>> pre-t null [1 + 2]
;-- null

>> pre-t false [1 + 2]
== 3

Prefix-ELSE flips this around. It looks at the first item for non-nullness, and if it isn’t null it returns that item. Otherwise it runs the block of code after it.

>> pre-e 10 [1 + 2]
;-- null

 >> pre-e null [1 + 2]
 == 3

 >>pre-e false [1 + 2]
 #[false]

Unlike EITHER-MATCH, I don’t know if I’ve really wanted this in practice.

MATCH-NULL/ELSE and MATCH-ANY-VALUE/ELSE feel more readable to me than EITHER-MATCH-NULL and EITHER-MATCH-ANY-VALUE. And they are useful in their own right, without the else.

So despite the historical hatred for IF/ELSE, I kind of feel like /ELSE on MATCH may make sense here. If anyone strongly disagrees, they can just say either-match: :match/else and be done with it!