Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should "interstitial" UNSET!s be ignored in "evaluative" contexts? #2248

Closed
Siskin-Bot opened this issue Feb 15, 2020 · 0 comments
Closed

Should "interstitial" UNSET!s be ignored in "evaluative" contexts? #2248

Siskin-Bot opened this issue Feb 15, 2020 · 0 comments

Comments

@Siskin-Bot
Copy link
Collaborator

Submitted by: fork

Note: Edited for brevity and relevance, see original ticket on Internet Archive

This question is about how unsets that appear "between" expressions should be handled across the language. Obviously, DO itself has a necessity to ignore these interstitial UNSET!s. Otherwise you couldn't call PRINT at all in a sequence of statements without getting an error. But what about other constructs?

To pick one that is currently tolerant (and surprises some) we can look at how ALL deals with UNSET! in #564:

    >> a: b: c: true
    >> all [a b print "Got here" c]
    Got here
    == true

PRINT returns UNSET! which is not a "truthy" value (it causes an error if used as a condition in an IF). Yet it does not disrupt the train of logic-sensing evaluations in an ALL. If ALL errored on UNSET!, you could still get use PRINT by putting it in a GROUP! with a TRUE to suppress the unset result:

    >> a: b: c: true
    >> all [a b (print "Got here" true) c]
    Got here
    == true

Compared with ALL ignoring UNSET!, this is a more general approach...since it would work even if PRINT did something like give back the string it had printed (or the PORT! output to).

But a more compelling case for the ignoring of unsets is seen with COMPOSE:

    >> condition: false
    >> compose/only [a b ( either condition ["Inserted???"] [] ) c]
    == [a b c]

Being uncommonly inserted as values in blocks, UNSET! is used in COMPOSE/ONLY to signal a desire to insert nothing. When values like even a NONE! are considered as meaningful, leveraging UNSET! in this way makes sense. On the whole, it's much more valuable to have a way out of inserting something vs. inserting an UNSET!.

Moving to another construct, the same issues can be considered with something like a CASE statement. Let's examine its evaluative slots:

    case [
        (print "Slot One")
        1 < 2 (print "Slot Two") [print "Should Happen"]
        (print "Slot Three")
        1 > 2 (print "Slot Four")  [print "Shouldn't Happen"]
        (print "Slot Five")
    ]

I'd argue that slots One/Three/Five are materially different from Two/Four. Two and Four resemble:

    >> if condition (print "Interstitial?  No.") [print "True Branch"]
     Interstitial?  No.
     == [print "True Branch"]

The paren doesn't represent an interstitial position. Nor do Two/Four in the CASE. Hence they should be taken as the body of the condition instead of the block.

Yet One/Three/Five are effectively "interstitials". Given that there is no way to act on an UNSET! condition, the choice is either to ignore them or give an error.

The variabilities introduced by allowing interstitial unsets, such as in CASE, can be seen as a parallel to #2245. Except instead of making the "shape" of the case depend on whether a condition is true or false, it's depending on whether an evaluation yields an unset or not. This variability can be bad for both humans and machines, in the sense that a compiled variant of the language (such as Red) can't say ahead of time what the structure is.

(Note: The CASE statement structure is already very liberal, so making it moreso is probably bad. Anything it can do to impose some order on top of its critical function are good--for instance requiring the condition/value pairs to come in complete pairs with no remainder. Skipping over these "interstitial" candidates for conditions that are UNSET! is almost certainly a bad idea...erroring as IF does is the more sane behavior, vs. not-erroring as "in-between IFs" do.)


Imported from: CureCode [ Version: r3 master Type: Issue Platform: All Category: Evaluation Reproduce: Always Fixed-in:none ]
Imported from: metaeducation#2248

Comments:

Rebolbot added the Status.important on Jan 12, 2016


Ladislav added Status.important, Type.wish, Type.note on Feb 7, 2016


Hostilefork commented on Dec 5, 2018:

Ren-C introduced the concept of NULL, which was something even "more unset than an UNSET!", as they were not values and could not be inserted into blocks. Hence they had no literal form--the only way you could get them in a CASE statement's block was if an expression evaluated to one.

For some time, NULL paralleled UNSET! in being neither true-nor-false. Routines like IF would error if nulls were used as a condition, while routines like ANY and ALL would handle them as "opting out of a vote", vs. erroring...just as R3-Alpha had treated UNSET!.

The desire of "opting out" of logical operators was eventually achieved in a much more general way, that could work for opting out of any evaluative context. This was a whole new category of function known as "invisibles", which provided (among other things) a fully invisible COMMENT. Rather than ask each construct to figure out how to react to a "non-valued value", the evaluator would do the required skipping to make it so they never saw it in the first place:

https://forum.rebol.info/t/issues-with-invisibles-a-truly-disappearing-comment/405

The result of PRINT could thus be neither true-nor-false, and cause an error, yet you could mitigate that simply by using an invisible like ELIDE to erase the result from visibility:

    >> a: b: c: true
    >> all [a b (elide print "Got here") c]
    Got here
    == true

More sophisticated operations like DUMP (which overtook --) provided one-stop shorthands for debugging-and-eliding at once:

    >> a: b: c: true
    >> all [a b (-- "Got here") c]
    -- "Got here"
    == true

This got to the root of the desire, while still allowing routines like PRINT to express their lack of a useful result with an "unfriendly" return value. As it happens, that result was ultimately decided to be a datatype called VOID! (a value type)...while NULL (still a distinct non-value) showed more merit as conditionally false.

That's a separate discussion. But the main point here is that what one might have once thought of as "interstitial unsets" are achieved in Ren-C with invisibles. These are indicated as routines whose return annotation has an empty typeset, invisible: func [return: [] ...] [...]


Hostilefork added Ren.resolved and Red.has.this.too on Dec 5, 2018


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants