DEFAULT now usable in CASE, SWITCH (!)


#1

In what I think is an impressive design point, you can now write:

case [
     condition1 [...]
     condition2 [...]
     default [
         ...code if neither condition1 or condition2 were truthy...
     ]
]

…as well as…

switch thing [
    match1 [...]
    match2 [...]
    default [
        ...code if thing <> match1 and thing <> match2...
    ]
]

How it works

It’s an interesting trick, that doesn’t involve making DEFAULT a keyword that CASE or SWITCH recognize. And it isn’t disruptive to the existing usage of DEFAULT for updating variables with no value, which looks like:

>> x: _
>> x: default [10 + 20]
>> print x
30

>> x: default ["already has value, won't take this one"]
>> print x
30

The mechanism actually hinges on the Fallout Feature of SWITCH, which was later added to CASE:

switch 10 [
    1 + 2 ["three"]
    3 + 4 ["seven"]
    uppercase copy "something else"
 ]

So that would evaluate to “SOMETHING ELSE”. Each switch match is evaluative, and if it has an evaluation followed by no BLOCK! to run, it just drops the evaluation out.

Really, all DEFAULT is doing is taking advantage of this. While it looks like it’s a condition and then a block of code to run on the match, the whole condition is default [...code...]. You could have also said (...code...) or do [...code...] and it would have worked. But DEFAULT cues the reader to know what you meant, which is important; and it also checks to make sure you don’t put more stuff after it.

switch 10 [
    1 + 2 ["three"]
    3 + 4 ["seven"]
    do ["something else"] [print "this will run"]
 ]

switch 10 [
    1 + 2 ["three"]
    3 + 4 ["seven"]
    default ["something else"] [print "this causes an error"]
 ]

Supplement to ELSE

While this appears to do the same thing as an ELSE, it’s actually different for a few reasons. A DEFAULT will be run even in CASE/ALL where no other cases matched, while ELSE only runs if one did:

case/all [
    1 < 2 [print "yep"]
    2 < 3 [print "yep"]
    default [print "this will run"]
]

case/all [
    1 < 2 [print "yep"]
    2 < 3 [print "yep"]
] else [
   print "this will not run"
]

It should also be noticed that indentation-wise, an ELSE statement’s code would be outdented one level from the contents of a DEFAULT. So DEFAULT is most useful when you have a short default item that fits on one line, that you want to cleanly keep inside the CASE’s block without introducing another outer block.

But the big mechanical reason one might favor DEFAULT is because ELSE forces completion of its left hand side. So you have to use a GROUP! (or <-) to prevent it from doing that with things like RETURN:

return (case [
    ...
] else [
   ...
])

; or

return <- case [
    ...
] else [
    ...
]

Otherwise, it would attempt to run the ELSE after (return case [...]) and that would be too late.

At the moment, this form of DEFAULT and the SWITCH/CASE fallout allow a NULL result. So you can make a DEFAULT case that signals NULL and use that to control a THEN and ELSE. I don’t know if this is a good idea or not, but since ELSE itself allows a null return, it seems to at least be consistent.

Now-outdated forms

In the past people have done this:

case [
    condition1 [...]
    condition2 [...]
    true [...]
]

To carry the intent a little better, some people would use a REFINEMENT! there, which is also truthy–but can make it a little more legible:

case [
    condition1 [...]
    condition2 [...]
    /else [...]
]

Both of those will still work, but I’d suggest not doing either of them anymore.

Since those tricks wouldn’t work for SWITCH, Rebol had historically offered a /DEFAULT refinement:

switch/default [
    match1 [...]
    match2 [...]
][
    ...code for no match...
]

With the introduction of ELSE and “switch fallout”, that refinement was deprecated and removed from SWITCH. But now, DEFAULT offers another choice, and maybe it will appeal to some people.


NULL, first-class values, and safety