MATCH is a handy tool for testing a value against some basic rules, and passing it through if they match...or evaluating to null if they don't. The rules can be combined in some interesting ways that make them rather powerful!
It uses the "match dialect". This looks pretty simple on the surface, like what you put in a function spec block for the legal types:
>> match [integer! tag!] 1020
== 1020
>> match [integer! tag!] "this text value won't match"
;-- null
>> match [integer! tag!] <matches!>
== <matches!>
But with the new features of generalized quoting, now you can test for quotedness.
>> match ['word!] first [foo]
;-- null
>> match ['word!] first ['foo]
== 'foo
It actually dereferences what you give it and sums up the quote levels. So you can do things like:
>> quoted-word!: quote word! ;-- Note: during transition, QUOTE is called UNEVAL
>> match 'quoted-word! first [''foo]
== ''foo
So since there is a quote level in the QUOTED-WORD! value, that gets added in with the quote on the match, so it looks for a doubly quoted value.
The premise is that each MATCH rule component is one item, and even types like INTEGER! are used...to test a length:
>> match 2 [a b]
== [a b]
>> match 2 [a b c]
;-- null
You can use single arity ACTION!s as well, if you use a GET-WORD! or GET-PATH! to indicate them:
>> match :odd? 304
;-- null
>> match :lib/even? 1020
== 1020
BLOCK! will OR rules together, PATH! will AND them
So this is a cool little trick:
>> match block!/2 [a b]
== [a b]
>> match text!/2 [a b]
;-- null
>> match text!/2 "ab"
== "ab"
>> match [block! text!]/2 "ab"
== "ab"
>> match '''[block! text!]/2 lit '''[a b]
== '''[a b]
>> match [integer!/[:even?] block!/[:empty?]] []
== []
>> match [integer!/[:even?] block!/[:empty?]] 1020
== 1020
Pretty cool huh? And as I mentioned, you can factor these rules out like in PARSE... note also that instead of :empty? you can just use 0.
>> even-int!: lit integer!/[:even?]
>> empty-block!: lit block!/0
>> match [even-int! empty-block!] []
== []
>> match [even-int! empty-block!] [a b]
;-- null
MATCH has an automatic erroring form, called ENSURE
If you want a quick and dirty way to typecheck something and pass it through, but error otherwise, use ENSURE.
>> ensure [even-int! empty-block!] [a b]
** Script Error: ...
>> ensure [even-int! empty-block!] 1020
== 1020
MATCH is now built in as a PARSE keyword...
The quoting features of MATCH were important for PARSE to help pick up the slack after LIT-WORD!.
>> did parse ['a b 'c d] [some [match [word! 'word!]] end]
== #[true]
There was a little bit of a incongruity previously, which is that MATCH did not want to quote its first argument. So you couldn't say match 'word! lit ['foo] and have it match, because the evaluator would strip off the quote. When all things were considered, it seemed to make more sense to have MATCH soft-quote its first argument, so it doesn't throw away the quote marks...but uses them in the rule.
PARSE then has a compatible expression, without a block! needed:
>> did parse ['a 'b 'c 'd] [some [match 'word!] end]
== #[true]
<opt>
, falsey values, and /ELSE
You might imagine a lot of code wants to say if match [...] whatever [...]. This could lead to unsatisfactory results if the thing you're matching is a null, blank, or logic false -- even though you matched, the falsey nature of the thing you were testing would foil your intent.
To help catch those errors, any falsey input that matches will be voidified. So at least you'll get a clear error if you used the result. But since voids are values, you'll be okay if you use THEN or ELSE
>> match [<opt> integer!] null then [print "matched, and void cued then!"]
matched, and void cued then!
There's also an /ELSE refinement, so you can provide a branch of code to run if there's no match...and it won't mutate the result at all:
>> match/else [<opt> integer!] null [print "didn't match" 100]
;-- null
>> match/else [<opt> integer!] #foo [print "didn't match" 100]
didn't match
== 100
Useful Dialect, Good Testbed for PATH!s
You can see a detail above of how I want to use things like :even? as a test, and then use PATH! for AND-ing tests together generically. But then, :even?/integer! is a GET-PATH!, while integer!/:even? would be an ordinary path. The meaning gets confuzzled... how would you specify a function with refinements, or otherwise get something out of a path?
obj: make object! [even-int: lit integer!/[:even?]]
match :obj/even-int 4
To get this distinction, we have to treat :[:obj]/even-int differently from :obj/even-int. And this really does suggest to me that the notion of allowing GET-WORD!s, SET-WORD!s, and LIT-WORD!s in PATH! is a mistake...it doesn't generalize and will fall down at the head and tail. Even when it works, it's ugly.
I think this is going in the direction of making PATH! a stronger dialecting part. And hopefully, with more good examples we can keep pushing on some of the other things (like "does Rebol need a date format with slashes in it, and if so can it be accomplished naturally as a PATH!")...