Theory of Symbols and Repurposing `? ?` and `! !`


#1

As I’ve said before: it’s good to try new things–and Ren-C has plenty of ideas that turned out to be winners (or led to a path of evolution to something that was a winner). but if a change doesn’t come out as a winner, it needs to be rethought and possibly backed out.

I think the use of ?? and !! as a kind of ternary operator has turned out to be a turkey.

(Please review “Backpedaling on non-BLOCK! branches” for some of where these guys came from)

These were chosen because they “stand out” a bit more than something like C’s condition ? true-thing : false-thing; ternary operator. That is why Perl6 chose them over simply ? and !. Doubling that makes them stick out like a sore thumb.

But can sticking out like a sore thumb be used better?

For a time I liked it, studying the construct in isolation. If you pulled up examples they looked okay. Yet taking a step back from the page, they looked odd in context.

Hence, this property may be something you want to do with constructs that are designed to be transient, and not committed in code. This makes me think that ??'s previous purpose as a debug construct might have been superior. Then as you visually scan the code you can tell if you’ve left debugging information in–it jumps right off the page. By a similar token, !! seems like it might make a great breakpoint.

This doesn’t give an answer to what would take the place of operators that do not “double evaluate” their branches.

>> 1 < 2 foo [a]
== [a]

>> 1 > 2 foo [a]
;-- null

>> null bar [a]
== [a]

>> 7 bar [a]
;-- 7

Solve for FOO and BAR. THEN and ELSE are taken for other purposes. And I’m saying ?? and !! have been tried, and don’t have the right feel.

It may turn out that Rebol doesn’t need these constructs. if condition [[a]] or condition and [[a]] don’t really look that bad. Nor does nullable else [[a]]. There’s probably a critical mass count of control constructs, and putting in too many may do more harm than good.

There’s also a kind of ugly property they have, where people might write condition ?? 1 + 2 !! 3 + 4, not realizing that both additions will run. This is true also of EITHER and IF, but people are unlikely to do calculations to make blocks that then execute (a “double-evaluate”) unless they’re at least somewhat sophisticated, and understand the way Rebol works.

All that said: if anyone can think of non-symbolic names for them, that could be interesting. I think it would be a mistake to just pick short available words that “kind of” line up (e.g. 1 < 2 so “x” but “y” looks okay yet it’s more or less gibberish.) We could go with single character operators as 1 < 2 ? “x” ! “y” which makes them blend in better with normal looking operators, but I still have my doubts on if that’s a fit for the kind of aesthetic we’d be looking for putting in the box.

I’m willing to just forget about “blockless control constructs”, for now. AND with OR, and using blocks, with a [[double-block if you *literally* wanted a block]], is close enough.

(Note that this ability of AND when it has a BLOCK! on the right is a more modern invention, and did not exist at the time ?? was made. Just another case of time and experience allowing a better solution to emerge.)


Deciding on an Alternative Comment Syntax
#2

Note: Here was the old Trello card content about ?? and !!


>> 1 > 2 ?? [greater than] !! [less than]
== [less than]

>> 2 > 1 ?? [greater than] !! [less than]
== [greater than]

>> print ["Two is" (2 > 1 ?? "not") "less than one"]
== Two is not less than one

>> if 1 < 2 [print "they mix"] !! [<like> #this]
they mix

>> if 1 > 2 [print "they mix"] !! [<like> #this]
== [<like> #this]

>> select [a b c] 'b !! "no-match"
== c

>> select [a b c] 'd !! "no-match"
== "no-match"

“The ?? and !! operators resemble the function of C’s condition ? true-clause : false-clause, but are much more flexible. They can be mixed and matched with other conditional constructs.”

“Because !! tests for nullness on its left, it’s a good fit for slipping in a default value when an operation returns nulls.”

“Like AND, ?? tests for logic on its left. There is an ?! operator which tests for logic false on the left. But currently no version that tests for valueness on the left (e.g. a parallel to THEN).”

“Note that if you say 1 < 2 ?? 3 + 3 !! 4 + 4, both additions will be run. To “block” evaluation, there has to be a BLOCK! somewhere (or a GROUP! that is quoted), hence these are not meant as a generic alternative to AND and ELSE.”


#3

Note: Here was the old implementation code for ??, !!, and a false-triggered variant called ?!


??: enfix func [
    {If TO-LOGIC of the left is true, return right value, otherwise null}

    return: [<opt> any-value!]
    left [<opt> any-value!]
    right [<opt> any-value!]
][
    if :left [:right] ;-- will voidify result if right is null
]

!!: enfix func [
    {If left isn't null, return it, else return value on the right}

    return: [<opt> any-value!]
    left [<opt> any-value!]
    right [<opt> any-value!]
][
    :left else [:right] ;-- will *not* voidify result if right is null
]

?!: enfix func [ ;-- name suggests shorthand of `left ?? () !! right`
    {If TO LOGIC! of the left is false, return right value, otherwise null}

    return: [<opt> any-value!]
    left [<opt> any-value!]
    right [<opt> any-value!]
][
    if-not :left [:right] ;-- will voidify result if right is null
]

#4

Having immersed myself into this mindset a bit, I am pretty much convinced of it. Because the idea of taking super-common debug routines and making them easy to type and spot is so compelling, I feel like PROBE has to be in that set. So I’ve been thinking this might be arranged such that:

  • -- is an alias for DUMP (now that we have the ME and MY operators)
  • ?? is an alias for PROBE
  • !! is an alias for BREAKPOINT (invisible)
  • ... is “TBD” (non-invisible/valued breakpoint, gives opportunity to resume w/value)

With “invisibles” like COMMENT and ELIDE rocking the scene (in a very good way), it’s extremely nice having acting as DUMP did, and being invisible. Especially since DUMP treats strings just as labels.

all [
   some-condition
   -- "got past first step"
   some-other [12 (condition) 34]
   -- "got past second step"
   more-stuff #running <whatever>
]

If you were putting in throw away debug output, why would you ever use a PRINT? DUMP is flexible and can be made even moreso. Here you have invisibility (doesn’t affect the ALL’s logic), and an easy way of spotting when you’ve left the debug code in.

(Note: I’ve been wanting to make an argument that PRINT should probably only take BLOCK! and TEXT!, for reasonings very similar to why there was backpedaling on non-block branches. This might help bolster that argument, since casual dumping and printing of variables for debugging would have a superior solution.)

It’s neat. So it feels like historical Rebol had a good thing going with this direction, and Ren-C has made that thing even better. Taking back -- for nobler purposes was a good step, and taking back == (see the IS and = plan) will be another forward step.

Apologies to Perl6, but we’re now back to using zero ideas from perl again. :slight_smile:


What to do with the lone ? character
Taking a Thrilling Tour Through the DUMP
Limiting PRINT to BLOCK!, TEXT! (perhaps BLANK! to opt-out?)
#6

Going further along these lines, I propose that ** not be infix power, rather that be just pow.

Remember that infix left-tight (the R3-Alpha way) grabs its immediate evaluated left argument:

>> power 2 2 + 1
== 8.0

>> 2 pow 2 + 1
== 5.0

And incidentally, that’s the name of the standard C library function for power.

After seeing how use-it-every-day useful -- has become, hopefully this gets everyone thinking… what cool symbolic purpose might ** serve? How about ++?