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

Make null "falsey", can't assign null w/SET-WORD!/PATH! #816

Merged
merged 1 commit into from
Jun 13, 2018
Merged

Make null "falsey", can't assign null w/SET-WORD!/PATH! #816

merged 1 commit into from
Jun 13, 2018

Conversation

hostilefork
Copy link
Member

@hostilefork hostilefork commented Jun 13, 2018

This is a watershed change. Firstly, nulls are considered false by
IF, CASE, ALL, ANY, DID, NOT, AND, OR, and other constructs. They no
longer give an error from existing in the indeterminate state of
"neither true nor false":

>> if () [print "now, falsey"]
;-- no result

That opens the doors to wider use of NULL as the uniform result for
"not found" or "no match" (this has also been called "soft failure").
Due to a progressive evolution, some routines had used BLANK! for this
purpose (ANY, FIND, MATCH, etc.) while others were using NULL
(SELECT, IF, CASE, etc.). Now it is uniform:

>> find [a b] 'c
;-- no result

>> any [false false]
;-- no result

>> all [true true () true]
;-- no result

>> match integer! <some-tag>
;-- no result

This uniform behavior opens the doors to using routines like ELSE and
ALSO with any routine that has "soft failure", as well as making it
easy to opt-out of operations without winding up with stray blanks:

>> data: copy [a b c]
>> append data all [
    1 < 2
    1 > 2
    'foobar
]
== [a b c] ;-- previously this would be [a b c _]

>> x: all [
       true
       select [a 10 b 20] 'c
   ] else [
       first [d e]
   ]
== d

>> pos: <not-found> unless find [a b c] 'd
== <not-found>

As a general policy, NULLs are "de-stigmatized" in cases like APPEND,
INSERT, COMPOSE, PRINT, etc. Since they are not ANY-VALUE! and cannot
be stored in blocks, they are reserved for the special intent of "no
value was intended". BLANK!s on the other hand are now committed to be
actual values which get appended or composed:

>> data: [a b c]
>> append data _
== [a b c _]

>> compose [a (_) b]
== [a _ b]

However, this commit brings back one aspect of "stigma" for NULLs in
that you cannot directly assign them via SET-WORD! or SET-PATH!, which
was true of UNSET! in R3-Alpha:

>> x: ()
** Error: Needs value

But with the widened scope of NULL usage, this means many things that
would not cause errors in R3-Alpha now will:

>> x: all [10 false]
** Error: Needs value

>> pos: find [a b c] 'd
** Error: Needs value

While this may seem like a drawback at first, it is actually a benefit.
With NULLs being casually accepted by APPEND and other places, having
them raise an error here is doing so at exactly the right point...it
is a "hot potato" in the sense of saying something must be done with
it before it is stored
. The easiest thing to do is to use TRY to
convert it to a BLANK!, or DID to convert it to a LOGIC!.

>> x: try all [10 false]
== _

>> pos: did find [a b c] 'd
== #[false]

In this way, erroring on the assign becomes an implicit assert. If
you write x: all [...], you are saying all those things are true,
and you want the last one
. If you say pos: find data item you are
saying it will be found. Needing other reactions is specifically
an expression of what you are doing if it's not. Making fixes to
accomodate this change actually IMPROVE the quality of the code.

Plus there are many other NULL-handling constructs now, which permit
clever rewrites, e.g.

if not pos: find data item [
   fail ["Could not find" :item]
]

; ...can avoid null assignment with...

pos: find data item else [
   fail ["Could not find" :item]
]

Most cases permit rewrites of this form. "SET/ANY" is brought back as
SET/OPT, and specialized as SET*.

>> set* 'x ()
;-- no value

>> set? 'x
== #[false]

Though a major step in the development of NULL, not all questions are
answered yet. But this is definitely an improvement.

@Mark-hi
Copy link

Mark-hi commented Jun 13, 2018

I am sure you meant any [false false] to return nothing, not any [true false], which I trust will still result in true.

Also I think it is necessary to explicitly state that all [true true () true] will be nothing, as that is a big difference from R2, bigger than all [true true ()] which already was nothing (unset).

This is a watershed change.  Firstly, nulls are considered false by
IF, CASE, ALL, ANY, DID, NOT, AND, OR, and other constructs.  They no
longer give an error from existing in the indeterminate state of
"neither true nor false":

    >> if () [print "now, falsey"]
    ;-- no result

That opens the doors to wider use of NULL as the uniform result for
"not found" or "no match" (this has also been called "soft failure").
Due to a progressive evolution, some routines had used BLANK! for this
purpose (ANY, FIND, MATCH, etc.) while others were using NULL
(SELECT, IF, CASE, etc.).  Now it is uniform:

    >> find [a b] 'c
    ;-- no result

    >> any [false false]
    ;-- no result

    >> all [true true () true]
    ;-- no result

    >> match integer! <some-tag>
    ;-- no result

This uniform behavior opens the doors to using routines like ELSE and
ALSO with any routine that has "soft failure", as well as making it
easy to opt-out of operations without winding up with stray blanks:

    >> data: copy [a b c]
    >> append data all [
        1 < 2
        1 > 2
        'foobar
    ]
    == [a b c] ;-- previously this would be [a b c _]

    >> x: all [
           true
           select [a 10 b 20] 'c
       ] else [
           first [d e]
       ]
    == d

    >> pos: <not-found> unless find [a b c] 'd
    == <not-found>

As a general policy, NULLs are "de-stigmatized" in cases like APPEND,
INSERT, COMPOSE, PRINT, etc.  Since they are not ANY-VALUE! and cannot
be stored in blocks, they are reserved for the special intent of "no
value was intended".  BLANK!s on the other hand are now committed to be
actual values which get appended or composed:

    >> data: [a b c]
    >> append data _
    == [a b c _]

    >> compose [a (_) b]
    == [a _ b]

However, this commit brings back one aspect of "stigma" for NULLs in
that you cannot directly assign them via SET-WORD! or SET-PATH!, which
was true of UNSET! in R3-Alpha:

    >> x: ()
    ** Error: Needs value

But with the widened scope of NULL usage, this means many things that
would not cause errors in R3-Alpha now will:

    >> x: all [10 false]
    ** Error: Needs value

    >> pos: find [a b c] 'd
    ** Error: Needs value

While this may seem like a drawback at first, it is actually a benefit.
With NULLs being casually accepted by APPEND and other places, having
them raise an error here is doing so at exactly the right point...it
is a "hot potato" in the sense of saying *something must be done with
it before it is stored*.  The easiest thing to do is to use TRY to
convert it to a BLANK!, or DID to convert it to a LOGIC!.

    >> x: try all [10 false]
    == _

    >> pos: did find [a b c] 'd
    == #[true]

In this way, erroring on the assign becomes an implicit assert.  If
you write `x: all [...]`, you are saying *all those things are true,
and you want the last one*.  If you say `pos: find data item` you are
saying *it will be found*.  Needing other reactions is specifically
an expression of what you are doing if it's not.  Making fixes to
accomodate this change actually IMPROVE the quality of the code.

Plus there are many other NULL-handling constructs now, which permit
clever rewrites, e.g.

    if not pos: find data item [
       fail ["Could not find" :item]
    ]

    ; ...can avoid null assignment with...

    pos: find data item else [
       fail ["Could not find" :item]
    ]

Most cases permit rewrites of this form.  "SET/ANY" is brought back as
SET/OPT, and specialized as SET*.

    >> set* 'x ()
    ;-- no value

    >> set? 'x
    == #[false]

Though a major step in the development of NULL, not all questions are
answered yet.  But this is definitely an improvement.

Because it is such a major change, it requires heavy use of Ren-C
features to bootstrap.  This puts the nail in the (already decided)
coffin of being able to bootstrap using R3-Alpha.  The version of
Ren-C that is being used is a year-old snapshot that has been working
fine, and is almost certainly more stable than R3-Alpha...so this is
not a problem.  As a consequence, the shim code is cut down to just
what is required to bring that Ren-C up to modern standards.

%rebmake.r is changed from being a module to just run with DO, to
ease the inheritance of the shim functions--which can't be changed
without risking breaking the mezzanine code of the boostrap tool.
@hostilefork
Copy link
Member Author

hostilefork commented Jun 13, 2018

I am of the opinion that this is the overwhelmingly correct answer to age-old questions like "should select [a 10 b 20] 'c return BLANK! or null." It seems to square up a lot of things that were head-scratchers, such as whether BLANK! should dissolve in COMPOSE or not (now we know, it shouldn't) or whether appending null to things should be an error or a no-op (now we know, it should be a no-op).

In chat I mentioned that I feel it is forgivable to have not been able to jump directly to this point. There were a lot of things floating in the mix--like people wanting to do a PRINT in the middle of either an ANY or middle of an ALL, and have it not disrupt the logic. When ELIDE didn't exist, it was easy to confuse this as being the mission of void, and until we had ELSE and new-UNLESS and all the routines that make working with NULL easier, this also wouldn't have seemed as viable.

Though it's been informally true for a while, I've marked this as the "point of no return" on using R3-Alpha to bootstrap. The committed Ren-C we've been using is now just over a year of usage, and it has been stable enough. No super big problems...though it does have a couple of bugs here and there. But fewer bugs than R3-Alpha, and more tools for working around them.

This allowed the compatibility shim to be streamlined, pretty much down to mostly the change
points for this commit
. Performance is becoming an issue overall, especially because CSCAPE does a complex PARSE based on REWORD and does it in several passes. So when you have to shim things like IF and ANY and ALL, you start paying for that slowdown even more.

To mitigate slowdown on those basic primitives I've done things like compose the ACTION! values into the function bodies so there's no word/path lookup. But there needs to be time spent on general study of what's taking up the most time in the make prep step, and how to speed it up while continuing the direction of making it less code and clearer to read.

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

Successfully merging this pull request may close these issues.

2 participants