REDUCE Religiosity: N Expressions in, N Values Out?

Turns Out That It's Not That Simple :puzzle_piece: :frowning:

Note that BLANK! (experienced as a comma in list rendering) Is Ignored By REDUCE

>> reduce [10 + 20, 3 + 4]
== [30 7]

I would assume BLANK! should also be ignored by predicates:

>> reduce:predicate [10 + 20, 3 + 4] negate/
== [30 7]

(Note: I haven't decided if REDUCE:WITH might be a better name, and I've also suggested that REDUCE/NEGATE might pass a predicate...as well as that the predicate refinement might take its argument positionally first... lots of questions there!)

This leads us to ask: Did it do an evaluation on the BLANK! and get a VOID!--and it ignores all voids? Or did it skip the evaluation step entirely on the BLANK?

:thinking:

One might think it would be "wasteful" to evaluate a COMMA!, and REDUCE should just skip it. But there is a question of RebindableSyntax... should you be able to change what commas do? Or are they exempt from overriding?

Vanishing Mixed With Non-VOID!-Taking Predicates

One also has to ask about how hard it should be to use a predicate function that doesn't take ghosts, with things like COMMENT or ELIDE:

I feel like this needs to work:

>> reduce:predicate [10 + 20, comment "hi", 3 + 4] negate/
== [30 7]

But if NEGATE handles VOID! at all, one would expect it to return a NULL antiform, which REDUCE wouldn't be able to put in the block.

So that means one of the following needs to be true:

  1. REDUCE always ignores VOID!s and doesn't pass them to the predicate

    • There might be some :GHOSTABLE refinement to say it passes them to predicates.

    • But COMMA! might need to be literally ignored even if you use :GHOSTABLE, otherwise if things like PACK were implemented as REDUCE:GHOSTABLE with a :PREDICATE of LIFT, you couldn't say pack [10 + 20, comment "hi"]

  2. REDUCE does datatype detection on the predicate, and if it accepts VOID! then it passes the ghost to the function...otherwise it does not.

I'd Been Trying (2), But That Has Fallen Down

I've always been a bit suspicious of the technique of testing the predicate function for voids.

It keeps breaking--for one reason or another. Most recently when I switched to the ability to Bypass Type Checking By Quoting Typespecs. Because it meant that if you used it with a function that did that, it would typecheck anything, so all functions with optimized type checking would receive ghosts...even if their typespec said they didn't.

That revealed a bit of an oversight--which is to say that if a function ignores its typespec when you run it, that doesn't mean the PARAMETER! you receive back from it should also be in "ignore" mode. Extracting parameters needs to flip the bit back to "it gets checked".

So it was fixable. But if you find a part that keeps giving you troubles, that's a bit of an indictment.

:face_with_head_bandage:

Tempting To Just Throw Out Voids

I will point out that R3-Alpha's REDUCE had several extra refinements:

USAGE:
    REDUCE value /no-set /only words /into out

DESCRIPTION:
    Evaluates expressions and returns multiple results.
    REDUCE is a native value.

ARGUMENTS:
    value

REFINEMENTS:
    /no-set -- Keep set-words as-is. Do not set them.
    /only -- Only evaluate words and paths, not functions
        words -- Optional words that are not evaluated (keywords) (block! none!)
    /into -- Output results into a block with no intermediate storage
            out (any-block!)

But Rebol2 and Red only have /INTO, and I don't even like that.

I think trying to make REDUCE be all things to all people seems like a mistake.

Offering VOID! to predicates is very rarely what you want. I've found one case (PACK). I could reasonably argue that if you're "weird" you should do the evaluations yourself...you've got EVAL:STEP and other lower-level tools.

(and in the case of PACK, it can actually reuse REDUCE's native implementation, twisted with an "act like PACK" flag.)

So I Guess I Was Mistaken (with default REDUCE, at least)

Under this set of premises, REDUCE:PREDICATE won't see voids, at least by default. I'd imagine that REDUCE-EACH would probably use the same rule.

:man_shrugging:

That seems the sane default. If down the road people complain enough, REDUCE might get a :GHOSTLY or :GHOSTABLE refinement. Until then, use EVAL:STEP to see ghosts.

(Unless we decide that EVAL--too--should skip voids by default and have a :GHOSTABLE refinement. But that sounds impure to me, I really doubt it.)