diff --git a/src/boot/natives.r b/src/boot/natives.r index 02c2551972..fc072de639 100644 --- a/src/boot/natives.r +++ b/src/boot/natives.r @@ -30,12 +30,12 @@ also: native [ ] all: native [ - {Shortcut AND. Evaluates and returns at the first FALSE or NONE.} + {Shortcut AND. Returns NONE vs. TRUE (or last evaluation if it was TRUE?)} block [block!] {Block of expressions} ] any: native [ - {Shortcut OR. Evaluates and returns the first value that is not FALSE or NONE.} + {Shortcut OR, ignores unsets. Returns the first TRUE? result, or NONE.} block [block!] {Block of expressions} ] diff --git a/src/core/n-control.c b/src/core/n-control.c index 91460b4b69..7bea650d18 100755 --- a/src/core/n-control.c +++ b/src/core/n-control.c @@ -251,23 +251,39 @@ enum { ** */ REBNATIVE(all) /* +** ALL is effectively Rebol's "short-circuit AND". Unsets do not vote either +** true or false...they are ignored. +** +** To offer a more generically useful result than just TRUE or FALSE, it will +** use as a "truthy" value whatever the last evaluation in the chain was. If +** there was no last value, but no conditionally false instance hit to break +** the chain, as in `all []` or `all [1 2 ()]`...it will return TRUE. +** +** (Note: It would become a more costly operation to retain the last truthy +** value to return 2 in the case of `all [1 2 ()`]`, just to say it could. +** The overhead would undermine the raw efficiency of the operation.) +** +** For the "falsy" value, ALL uses a NONE! rather than logic FALSE. It's a +** historical design decision which has some benefits, but perhaps some +** drawbacks to those wishing to use it on logic values and stay in the +** logic domain. (`all [true true]` => true, `all [false true]` is NONE!). +** ***********************************************************************/ { REBSER *block = VAL_SERIES(D_ARG(1)); REBCNT index = VAL_INDEX(D_ARG(1)); - // Default result for 'all []' SET_TRUE(D_OUT); while (index < SERIES_TAIL(block)) { index = Do_Next_May_Throw(D_OUT, block, index); if (index == THROWN_FLAG) return R_OUT_IS_THROWN; - if (IS_CONDITIONAL_FALSE(D_OUT)) { - SET_TRASH_SAFE(D_OUT); - return R_NONE; - } + if (IS_CONDITIONAL_FALSE(D_OUT)) return R_NONE; } + + if (IS_UNSET(D_OUT)) return R_TRUE; + return R_OUT; } @@ -276,6 +292,18 @@ enum { ** */ REBNATIVE(any) /* +** ANY is effectively Rebol's "short-circuit OR". Unsets do not vote either +** true or false...they are ignored. +** +** See ALL's notes about returning the last truthy value or NONE! (vs. FALSE) +** +** The base case of `any []` is NONE! and not TRUE. This might seem strange +** given that `all []` is TRUE. But this ties more into what the questions +** they are used to ask about in practice: "Were all of these things not +** false?" as opposed to "Were any of these things true?" It is also the +** case that `FALSE OR X OR Y` matches with `TRUE AND X AND Y` as the +** "seed" for not affecting the chain. +** ***********************************************************************/ { REBSER *block = VAL_SERIES(D_ARG(1)); @@ -285,7 +313,7 @@ enum { index = Do_Next_May_Throw(D_OUT, block, index); if (index == THROWN_FLAG) return R_OUT_IS_THROWN; - if (!IS_CONDITIONAL_FALSE(D_OUT) && !IS_UNSET(D_OUT)) return R_OUT; + if (IS_CONDITIONAL_TRUE(D_OUT)) return R_OUT; } return R_NONE; diff --git a/src/core/n-loop.c b/src/core/n-loop.c index df599f6c89..90df852b88 100755 --- a/src/core/n-loop.c +++ b/src/core/n-loop.c @@ -382,7 +382,10 @@ typedef enum { Val_Init_Object(D_ARG(1), frame); // keep GC safe Val_Init_Block(D_ARG(3), body); // keep GC safe - SET_NONE(D_OUT); // Default result to NONE if the loop does not run + if (mode == LOOP_EVERY) + SET_TRUE(D_OUT); // Default output is TRUE, to match ALL MAP-EACH + else + SET_NONE(D_OUT); // !!! Loops may set default to not set in future if (mode == LOOP_MAP_EACH) { // Must be managed *and* saved...because we are accumulating results @@ -566,13 +569,7 @@ typedef enum { if (!IS_UNSET(D_OUT)) Append_Value(out, D_OUT); break; case LOOP_EVERY: - if (every_true) { - // !!! This currently treats UNSET! as true, which ALL - // effectively does right now. That's likely a bad idea. - // When ALL changes, so should this. - // - every_true = IS_CONDITIONAL_TRUE(D_OUT); - } + every_true = every_true && IS_CONDITIONAL_TRUE(D_OUT); break; default: assert(FALSE); @@ -634,10 +631,13 @@ skip_hidden: ; // Result is the cumulative TRUE? state of all the input (with any // unsets taken out of the consideration). The last TRUE? input - // if all valid and NONE! otherwise. (Like ALL.) If the loop - // never runs, `every_true` will be TRUE *but* D_OUT will be NONE! - if (!every_true) - SET_NONE(D_OUT); + // if all valid and NONE! otherwise. (Like ALL.) + if (!every_true) return R_NONE; + + // We want to act like `ALL MAP-EACH ...`, hence we effectively ignore + // unsets and return TRUE if the last evaluation leaves an unset. + if (IS_UNSET(D_OUT)) return R_TRUE; + return R_OUT; default: diff --git a/src/include/sys-value.h b/src/include/sys-value.h index d9545faac6..2e0af4b0a4 100755 --- a/src/include/sys-value.h +++ b/src/include/sys-value.h @@ -823,11 +823,13 @@ struct Reb_Position // Conditional truth and falsehood allows an interpretation where a NONE! is // a FALSE value. These macros (like many others in the codebase) capture // their parameters multiple times, so multiple evaluations can happen! +// +// Unsets are neither conditionally true nor conditionally false. #define IS_CONDITIONAL_FALSE(v) \ (IS_NONE(v) || (IS_LOGIC(v) && !VAL_LOGIC(v))) #define IS_CONDITIONAL_TRUE(v) \ - !IS_CONDITIONAL_FALSE(v) + (!IS_UNSET(v) && !IS_CONDITIONAL_FALSE(v)) /***********************************************************************