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

Unsets "no vote" for ALL and ANY, won't return them #129

Merged
merged 1 commit into from
Oct 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/boot/natives.r
Original file line number Diff line number Diff line change
Expand Up @@ -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}
]

Expand Down
40 changes: 34 additions & 6 deletions src/core/n-control.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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));
Expand All @@ -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;
Expand Down
24 changes: 12 additions & 12 deletions src/core/n-loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion src/include/sys-value.h
Original file line number Diff line number Diff line change
Expand Up @@ -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))


/***********************************************************************
Expand Down