Construct For Updating Variable With Value (If It's Not Void)

This is a construct that I think @gchiu originally suggested, which is kind of a reverse-DEFAULT. A variable is optionally updated, but left as-is if the value you try to update to is void.

>> x: <a>
== <a>

>> x: perhaps if false [<b>]
== <a>

>> x
== <a>

>> x: perhaps if true [<c>]
== <c>

>> x
== <c>

An implementation of this would look something like:

perhaps: enfixed func* [
    "Set word or path to a default value if that value is a value"
    return: [<opt> any-value!]
    'target [set-word! set-path!]
        "The word to which might be set"
    optional [<opt> <void> any-value!]
        "Value to assign only if it is not void"
][
    if semiquoted? 'optional [
        ;
        ; DEFAULT requires a BLOCK!, PERHAPS does not.  Catch `x: perhaps [...]`
        ;
        fail 'optional [
            "Literal" type of :optional "used w/PERHAPS, use () if intentional"
        ]
    ]

    ; Note that right evaluates before left here:
    ; https://github.com/rebol/rebol-issues/issues/2275
    ;
    if null? :optional [return get/any target]
    return set target :optional
]

It's a nice thing to have, but I'm not sure what to call it. Once upon a time it was called MAYBE, before the term was taken for the purpose of converting nulls to voids (which I think is a good fit).

PERHAPS seems too indistinguishable from MAYBE, and I don't know there's any clear reasoning as to why it would be called that.

I think UPDATE was suggested:

 >> x: 10
 == 10

 >> x: update void
 == 10

>> x 
== 10

>> x: update 20
== 20

>> x
== 20

But I dunno about that either. Ideas?

1 Like

For anyone curious about what this is about, it pertains to the use of VALUE_FLAG_UNEVALUATED, which remains controversial in terms of how much it complicates the implementation vs. the value it provides.

PERHAPS is similar looking to DEFAULT, so similar that you might think it requires a BLOCK! on its right. But while DEFAULT conditionally runs the code on the right, PERHAPS always runs it. So a BLOCK! on the right as a literal would undermine it, as the PERHAPS would always do the assignment.

>> x: null
>> x: default [1000 + 20]
>> x
== 1020

>> x: perhaps [case [1 > 2 [<nope>] 3 < 4 [<yep>]]
== [case [1 > 2 [<nope>] 3 < 4 [<yep>]]] // whoops

For this reason it put in a protection against literal uses. That has really been helpful, because it's easy to get wrong. I definitely do make that mistake, and I wrote PERHAPS--so I'm sure other people would.

Or is the better solve to make PERHAPS take a hard-quoted GROUP! and run it even though it doesn't need to?

>> x: perhaps case [1 > 2 [<nope>] 3 > 4 [<nope>]]
** Error: PERHAPS only takes a GROUP!

>> x: perhaps (case [1 > 2 [<nope>] 3 > 4 [<nope>]])
>> x
== 10

>> code: [case [1 > 2 [<nope>] 3 > 4 [<nope>]]]
>> x: perhaps code // checking evaluated bit wouldn't error on this
** Error: PERHAPS only takes a GROUP!

>> code: [case [1 > 2 [<nope>] 3 > 4 [<nope>]]]
>> x: perhaps (code)
>> x
== [case [1 > 2 [<nope>] 3 > 4 [<nope>]]]

That is actually safer. But where does the protection stop? What property of IF makes it not require a GROUP!, while PERHAPS does? In this case, the argument might be that PERHAPS sure looks a lot like DEFAULT...and with it seeming to be in the same family, it's easy to screw up.

Put another way: It's hard to say that PERHAPS doesn't need some kind of protection, and literally quoting its right hand side and expecting a GROUP! there is a way of accomplishing that. But requiring a GROUP! where it's not necessary has the downside of interfering with COMPOSE operations when it doesn't have to. Requiring a BLOCK! doesn't convey the "always executed, not conditionally executed" nature. This makes checking the unevaluated bit an attractive compromise.