Questions About "Wrapped" Things (QUOTED!, QUASI!)

I've been held up on the new generalized isotopes due to looking at examples like this one:

attempt: func [
    return: [<opt> any-value!]
    code [block!]
    <local> last'
][
    last': the ~
    reduce-each ^result' code [
        if error? result' [return null]
        if @void = result' [continue]
        last': result'
    ]
    return unmeta last'
]

Here you see ATTEMPT running a REDUCE-EACH, but asking for the ^META of each expression. Asking for the result in meta form suppresses the automatic promotion of a definitional error (isotopic) to a generic failure. (Re-read the definitional failure post for a refresher.)

The meta of the isotopic error was received as a plain ERROR!. And it handles it by returning null. A void state is handled by continuing--leaving the cumulative result as-is.

(I've explained why the meta state for void is weird. For the sake of argument, let's say it doesn't change under the new rules.)

The BAD! Touch

I've enumerated the reasons why having isotopes generate a ^META which is actually a "BAD! ERROR!" has several advantages. Wrapping up meta in BAD! makes it a better generalized inverse evaluator (UNEVAL) as well as making all the meta states truthy.

But I'm displeased with the impact this has here.

    last': the ~
    reduce-each ^result' code [
        all [
            bad? result'
            error? unbad result'
        ] then [
            return null
        ]
        if @void = result' [continue]
        last': result'
    ]
    return unmeta last'

Obviously the naming is... bad. But I don't like not being able to ask it in a single step, as I could before. It makes it tempting to introduce constructs like BAD-ERROR?

    last': the ~
    reduce-each ^result' code [
        if bad-error? result' [return null]
        if @void = result' [continue]
        last': result'
    ]
    return unmeta last'

Can We Avoid Making BAD-XXX? or QUOTED-XXX? Tests

One strategy could be if there was some kind of arity-2 BAD test, like:

>> badly? integer! (first [~10~])
== #[true]

This would avoid having to fill the global namespace with a bunch of things like BAD-INTEGER?

You might get the idea that a TYPE OF could encode this, like:

'~integer!~ = type of (first [~10~])

But that's not a BAD! DATATYPE! you're getting on the left. It's a BAD! WORD!, and we don't have any kind of WORD!-to-DATATYPE! equivalence (there are reasons for this, beyond the scope of this thread).

Maybe there's a dialect of containment of some kind (?) like:

 match [bad!.integer!] (first [~10~])

Given some recent suggestions, it might be possible to say something like:

if integer? try unbad (first [~10~])

The concept being that if it wasn't wrapped up like ~10~, then you'd get the original value out of the TRY instead of a type error. But then this would be conflated if your original input had been an integer. It would work for the error case since things that aren't bad are quoted, but it doesn't feel good.

It Seems We Should Have a Way Of Asking This...

I look at the ^META code where it doesn't have the bad wrapper on it, and I think "that's so much cleaner". When you multiply it across all the other places that want to process meta arguments, it just feels wrong to throw in all the extra complexity.

Yet it feels like something of a failure that we don't have an easy way of asking if a value is a datatype wrapped in tildes or not. It's a weakness that exists in our ability to ask questions about quoted types as well.

Ultimately, is it the best solution to backpedal on the QUOTE/META unification and accept different tracks?

  • META & UNMETA - promotes isotopes to undecorated form, NULL <=> NULL, promotes non-isotopes to one level quoted higher than they were. Because it can generate blanks and logic false, NULL is not the only falsey result you can receive from a META operation.

  • QUOTE & UNQUOTE - promotes isotopes to their ~bad~ value forms, NULL <=> single apostrophe ('), quotes everything else. Acts as a proxy for "UNEVAL/EVAL" of single values.

Then the question remains about what construct something like FOR-BOTH should use. It wants to act like QUOTE and UNQUOTE except with NULL and VOID passthrough.

Under these rules, it's really a special "don't quote quite everything" version of QUOTE so maybe less is more, and just give it an asterisk... to say "quote but with some kind of twist".

for-both: lambda ['var blk1 blk2 body] [
    unquote* all [
        quote* for-each (var) blk1 body
        quote* for-each (var) blk2 body
    ]
]

I can't think of what this could be as a refinement. QUOTE/PASSTHRU-NULL-AND-VOID? QUOTE/WITH-HOLES-IN-IT? QUOTE/LEAKY? QUOTE/USUALLY?

Not sure, but I really feel like these should be true by default:

>> quote null
== '

>> quote void
** Error: VOID has no quoted form, use META (or QUOTE* if you want to passthru)

Anyway, this is just the same thing from a week ago running around, and I can't make progress until I pin it down! I'll sleep on it a bit.

Okay, I did sleep on it. Then I woke up deciding I'm probably not going to come up with a better name for "BAD!" than QUASI!

Setting that down, it makes the code a bit less infuriating to me.

last': the ~
reduce-each ^result' code [
    if (quasi? result') and (error? unquasi result') [return null]
    if @void = result' [continue]
    last': result'
]
return unmeta last'

I'll point out that if we decide that the void state is best treated as being truly unset, this would instead look like:

last': the ~
reduce-each ^result' code [
    if unset? the result' [continue]
    if (quasi? result') and (error? unquasi result') [return null]
    last': result'
]
return unmeta last'

I'm using unset? the result' instead of unset? 'result' due to variables with leading and trailing quotes being confusing.

Were we to introduce a new test for isotopic errors that would tolerate them and not promote them to raised errors, we might call it FAILURE?. It might have to be first, or it might be argued that things that take their arguments as ^META like VOID? would not trigger failures:

last': the ~
reduce-each ^result' code [
    if failure? :result' [return null]
    if void? :result' [continue]
    last': result'
]
return :last'

Similar tests like SPLICE? might test for isotopic blocks, to make this style of coding easier.

Moving in the direction of these changes involved, it doesn't feel so horrible. And it brings us back to our happy place with FOR-BOTH.

for-both: lambda ['var blk1 blk2 body] [
    unmeta all [
        meta for-each (var) blk1 body
        meta for-each (var) blk2 body
    ]
]

It actually looks like there's a kind of odd completion here, if the META of VOID is always void, but we say the only way a variable can convey voidness is through the approximation of being unset.

Previously it was a subtlety of the word form of META when compared with the operator form of ^ to say that the word form would pass through voids "as is" instead of creating a weird signal like @void--hence being usable in something like FOR-BOTH. But if the meta form is actually an unset variable, we can say that's really just the closest thing you can get to the "ground zero" meta...where meta of void is void.

Also, when I started thinking seriously about the idea that ^META things wouldn't be decorated as ~QUASI~ items, it makes the code harder to interpret:

 reduce-each ^item' block [
    if _ = item' [print "React to a 'none' variable, e.g. unset"]
    if 'null = item' [print "React to a null isotope"]
    ;...
 ]

The relationship or meta status isn't reinforced, the way it does if the quasi state is carried along:

 reduce-each ^item' block [
    if '~ = item' [print "React to a 'none' variable, e.g. unset"]
    if '~null~ = item' [print "React to a null isotope"]
    ;...
 ]

Here you see QUASI!-things and you think "oh, something meta must be going on". That seems stronger.

No promises that this answers everything, but I feel better about it.

2 Likes