MATCH in Rust vs. SWITCH

We've discussed how SWITCH should deal with the "rhythm" of things matched vs. branches to take.

I was programming in Rust a bit, and wanted to point out their MATCH...which is like SWITCH, where it uses | how comma has been proposed:

match number {
    1 => println!("One!"),
    2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
    13..=19 => println!("A teen"),
    _ => println!("Ain't special"),
}

Compared to the proposal:

switch number [
    1 [print "One!"]
    2, 3, 5, 7, 11 [print "This is a prime"]
    ; no equivalent yet proposed for ranges
] else [
    print "Ain't special"
]

It's worth thinking about how ranges and range testing are supposed to be expressed.

But the bigger point is that I think I like the comma for meaning a list of alternates in this context...enough to say that SWITCH should require it if you want multiple conditions for the same branch. This would solve asymmetries in CASE and SWITCH and let them share more code.

4 Likes

Something that has occurred to me is that SWITCH feels a bit weak when it comes to wanting to run a function besides equality on something.

Which gave me a thought. Let's imagine at least some form of SWITCH required you to put in a => between the thing you're matching and the clause:

 x: 10

 switch/all x [
     match integer! => [print "It's an integer!"]
     "bobcat" <> => [print "It's not a bobcat"]
     <something> => [print "Basic matches would still work"]
 ]

The idea here would be that if the left hand side of the => has too few arguments when it hits the =>, then the missing argument is supplied as the thing that you're SWITCH-ing on.

This seems so much more powerful than traditional SWITCH that I am seriously tempted to make this SWITCH, and then make what we've been calling SWITCH be "CHOOSE".

An Agnostic SWITCH That Engages Dialecting

This was inspired by thinking about the issue of range-checking, and going... "uh, does SWITCH really want to get involved in that? Like, seeing 1..10 as a TUPLE! and interpreting that?"

This way you can have a general function that is dialected to do that whenever you need it. Imagine it is called IN-RANGE. And you could use it anywhere:

>> value: 5

>> in-range 1..10 value
== true

Then... if SWITCH supplies expressions an argument if one is missing, you can make a switch clause just in-range 1..10. Omit the value, but switch supplies it.

switch value [
    ...
    in-range 1..10 => [<whatever>]
    ...
]

Now you haven't rigged SWITCH to be a bizarre dialect, but you get all the benefit.

What is Seen Cannot Be Unseen :eyes:

Today's simpleminded switch should be CHOOSE.

CHOOSE is a suckier word for a suckier--but still useful and brief--idea.

2 Likes

So I decided I actually think I like vertical bar for this:

switch number [
    1 => [print "One!"]
    2 | 3 | 5 | 7 | 11 => [print "This is a prime"]
    in-range 13..19 => [print "A teen"]
    print "Ain't special"  ; leveraging "fallout"...last expression as default
]

Even more rustlike. It has parity with PARSE expressing "alternates", and it leaves comma available for separating clauses, especially important in CASE/CHOOSE when you're putting things on one line.

>> choose (10 / 2) [1 | 2 [print "small"], 3 | 4 | 5 '<big>]
== <big>

Actually... Commas might be good for AND'ing vs. OR'ing

switch number [
    > 20, even? [print "Greater than 20 -and- even"]
    < 20 | odd? [print "Less than 20 -or- odd"]
]

Hard to think of another symbol for that. It puts the AND clauses in sequence--kind of like a parse rule--where all would have to apply.

3 Likes

Because I want to really be throwing Ren-C up against non-trivial examples, I thought I'd give this a shot.

My first draft actually seemed to work pretty well!

switch2.control.test.reb

It uses the fledgling ability to turn a block of code into a FRAME!. It needs some work, but it's reasonably all right:

>> parameters of :match
== [test value]

>> make frame! make varargs! [match integer!]
== make frame! [
    return: '
    test: #[datatype! integer!]
    value: ~end~
]

If you notice something interesting there, when you tried to MAKE FRAME! on an "incomplete sentence" it didn't give you an error in the moment. Instead, it gave you a frame with an ~end~ isotope in the missing slot.

The trick--you may remember--is that frames can't actually take isotopes as arguments. So had there been an actual literal ~end~ there it would be an error condition...it would have to be a ^META parameter, in which case it would show up in the argument as a quoted ~end~ BAD-WORD!.

In any case, what my primitive test does is just look to see if there's an ~end~ isotope in a plain parameter slot, and fills the first one it sees with the switch value.

It's Probably Too Sketchy to Use This Way...

Looking at it realistically, I think it's probably bad to be making such assumptions. It's probably better to have some sort of explicit marker, like a BLANK! or otherwise to show your intent:

switch/all (...some-long-expression...) [
    match integer! _ => [print "It's an integer!"]
    "bobcat" <> _ => [print "It's not a bobcat"]
    <something> => [print "Basic matches would still work"]
]

It seems especially important if there's a fallthrough to plain equality matching.

But still--the FRAME! tricks are pretty good. And I want to keep going after these demos...trying to clean up the rough edges until they really read right.

This example gave me some insights into UPARSE. I decided that the new ANY [...] is just too clear and good to need an @ on the block (like ANY @[...]), just to try and warn you that the block wasn't going to be interpreted in the normal ways by the BLOCK! combinator. That's Rebol's m.o....you're always in new contexts and the rules change. It's comprehensible, so no reason to make it look bad!