Brave SWITCH new .WORLD [?] - Skippable Predicates

Note: This post was originally written to use REFINEMENT! for the predicates. However, generalized TUPLE! has seemed more favorable, so that's what's used when talking about it now...even though generalized tuples do not exist at time of writing.

It's a bummer that all the Rebol functions like SWITCH or FIND use some kind of internal magic operator to decide if things are equal. We'd like that operator to be less opaque and more clearly documented and understandable, and it's in the long to-do-list... to try and figure out how it ties into IS-ness or MATCH-ness or whatever it is.

Still, no matter what the default is, we know we need a way to override it. Ren-C had added a /STRICT refinement to SWITCH to deal with this case:

>> switch 0.01 [1% [<pretty lax...>] 0.01 [<stricter>]]
<pretty lax...>

>> switch/strict 0.01 [1% [<pretty lax...>] 0.01 [<stricter>]]

But the name "strict" wasn't something super agreed upon. And it's just the tip of the iceberg, you kind of want to pass in more functions. That seems kind of ugly.

>> switch/compare 0.01 [1% [<pretty lax...>] 0.01 [<stricter>]] :==

Fancy New Experiment: Skippable Refinements that ARE the compare!

Dig this (and it will look even better when EQUAL? and = are strict by default, and IS is the lax operator...)

>> switch 0.01 [1% [<pretty lax...>] 0.01 [<stricter>]]
<pretty lax...>

>> switch 0.01 /strict-equal? [1% [<pretty lax...>] 0.01 [<stricter>]]

>> switch 0.01 /== [1% [<pretty lax...>] 0.01 [<stricter>]]

There's a skippable slot for a "refinement" (blank-headed PATH!) between the thing you're switching on and the cases block. And it looks up to an ACTION!.

It also accepts literal ACTION!s. You won't/can't type that in by hand...but that means if you're doing an APPLY there's a normal frame slot for your compare to go into. (It's one of the nice things about skippable parameters being knownst to the system in a way that variadic parameters are not.) If you need a SWITCH variation with a different interface you can make one out of this one.

Notice that the interpretation puts the switched-on expression on the left or in the first argument.

>> x: 10
== 10

>> switch x /greater? [20 [<nope>] 5 [<yep>]]
== <yep>

Before we go propagating the idea all over the system, why not give this a spin. Kick the tires a bit. Write some tests!

>> x: 10

>> switch x /(reduce pick [:greater? :lesser?] 1) [20 [<a>] 5 [<b>]]
== <b>

>> switch x /(reduce pick [:greater? :lesser?] 2) [20 [<a>] 5 [<b>]]
== <a>
1 Like

I feel like maybe this should be done with generalized TUPLE! instead.

switch obj/value /matches [...]

switch obj/value .matches [...]

Because the plan is that TUPLE!s will be inert, there's not much odds of you switching on one literally. But PATH!s you will want to switch on.

As the approach goes on, this will make it harder to do things like FIND on explicit tuples

do [find data .matches integer!]  ; yay, works

do [find css-stuff .class]  ; oops, tries to call CLASS as the predicate

But with generalized quoting, you'll be able to get around it:

do [find css-stuff '.class]

do [find css-stuff lit .class]

You just have to understand that TUPLE! has a purpose that is "effectively evaluative", kind of like you have to understand when you go to find WORD!s. find data my-word already needs a quote or lit. It comes with the territory.

I'm a little bummed because it kind of feels like these "are refinements", but I think that re-imagining it and saying tuples starting with dot act as predicates (by convention) probably has better bones.

1 Like

I find that tuple looks better, and wins when it comes to ergonomics.


Using something different than refinement could also have another advantage, that it could be interpreted as a kind of chain. If we assume SWITCH usually goes with IS, you might want to use = for strict equality. You also might want to use a more literate "equals". But there isn't any particularly nice way to say "unequals".

So what about just good old:

 switch x .not.equals [

Then imagine if we've define IS as is: :equals/lax. So there's a refinement on equals, and you might also call it as:

 switch x .not.equals/lax [

So that's an interesting potential advantage. Though the proposal I have for tuples is that paths take priority, so it would see that as the PATH! [.not.equals lax]. But if the chain is specializing that should still work, e.g. (=> NOT EQUALS) could produce a function that takes one argument and has a /LAX refinement.

(This is all pretty out there, and easier said than done...but it does tie into stuff I'd like to see be able to work. I'm just putting in another point of notational advantage for TUPLE! here.)

This also suggests the possibility that a GET-TUPLE! might have a purpose as a chaining generator, e.g.

 chain [:a | :b | :c]
      => get c.b.a 
      => :c.b.a

We also might consider if CHAIN should be reversed, which is something that came up before.

1 Like

Predicates are becoming more critical to think through, as Ren-C tries to pin down some formalisms for off-the-cuff Rebol2-isms.

For instance, consider how Rebol2 blurred lines by considering both literal datatype matches and values-of-that-type to match:

 rebol2> find ["A" 10 20] integer!
 == [10 20]

 rebol2> find reduce [string! integer! block!] integer!
 == [integer! block!]  ;...and, it looks like WORD!s :-/

In moving to using the @[bl o ck] type to represent datatypes, Ren-C is making this more formal:

>> integer!: @[integer]

>> find reduce [text! integer! block!] integer!
== [@[integer] @[block]]  ; distinct from WORD!s

By and large, the interpretation most natives (including PARSE) will be taking is that often when you are using such an @[bl o ck], you mean to specify a type. But not always. Your dialects are free to interpret these other ways.

So I feel that if you want to do a FIND where you're matching against a pattern, that's a job for a predicate. But where should it go? How should the parameters be mapped?

Consider that you can write match integer! 10 and get 10, but match integer! <tag> and get null. That means the datatype parameter comes first and the thing to test comes second. So this predicate looks a bit backwards:

 find /match ["A" 10 20] @[integer]

There's a routine called MATCHES which reverses the order:

>> 10 matches integer!
== 10

So we might use that instead:

find /matches ["A" 10 20] @[integer]

But would it look better if it were between ?

find ["A" 10 20] /matches @[integer]

Or does that only apply for things that are infix? It's hard to say.

But what I do think is that for a "fancy function" that does something other than an equality comparison on FIND, you should have to ask for it. We should make it easy to ask, and then people will likely appreciate the more deterministic and reliable behavior.

There's a technical problem I hadn't noticed until REFINEMENT! and PATH! unified (which we may discuss) but that is that PATH!s are not inert. This means it's hard to distinguish the above from:

 >> find [a b c] obj/thing-to-find   ; evaluative
 ** Error: predicate is not an ACTION!

e.g. the hard quote recognition of a PATH! sucks in things you might legitimately want to look up and find. :-/ You don't want to have to subvert this with find [a b c] (obj/thing-to-find) or other nonsense.

I'm still rather bullish on /a/b being both legal as something in the parts box and the "same-flavored" thing as /a. I'm also not confident that the evaluative advantage of an inert /a exceeds being able to get that with '/a. We may not know what the purpose of that leading slash could be, but it is presumably related to how binding is treated.

Yet I mentioned that I thought having TUPLE! be the inert form of PATH! was an interesting idea, as it would provide historical compatibility as well as open up new options. We don't have it yet, but its inert status would make it the clear better choice for this function.