REDUCE Religiosity: N Expressions in, N Values Out?

The historical rule is that there are no vaporizations by default in REDUCE. If you have N expressions in, you will have N values out.

There is a religiosity about it in Red. :roll_eyes:

If you think reduce [eval []] should be [] then that would require both:

  • EVAL [] to be void, and
  • REDUCE to vaporize voids

Like this:

 >> if 1 = 2 ["not"]
 == \~\  ; antiform (void!)

 >> reduce ["This will" (if 1 = 2 ["not"]) "make you a Red heretic"]
 == ["This will" "make you a Red heretic"]

(It would still be possible to use REDUCE:PREDICATE and use a predicate function that errors on voids.)

In practice, I don't tend to use REDUCE that often compared to COMPOSE. reduce ['x: 1 + 2] seems awkward compared to compose [x: (1 + 2)]. So it's like the only time I would use REDUCE would be to build the "block of precisely N values".

Hence being restricted to N-in, N-out hasn't bothered me personally so far. :man_shrugging:

YMMV.

I hadn't pondered the absolutism of REDUCE. I've embraced your concept of vaporization for UNSPACED/SPACED/COMPOSE and don't see why REDUCE would be different. I notice that Ren-C (in R3C as well as current) does vaporize in the case of reduce [( )] which to mind is the same thing.

1 Like

Since 2020 there has been significant evolution in the system, with VOID! and "heavy void" (empty PACK!) splitting roles for safety:

https://rebol.metaeducation.com/t/why-comment-vanishes-but-not-eval-of-comment/2563

So one question is to ask if REDUCE should erase VOID! returned by a vanishable function, e.g. should reduce [1 + 2 comment "hi"] be [3].

But generally speaking: a function like REDUCE would not hook into the evaluator at a granularity where it asks if a specific function is vanishable or not. It only sees whether it gets void or heavy void from an evaluation, and it decides whether it wants to erase certain voids or error on them.

And if it does not erase heavy voids, we can also ask if it's doing evaluations in a way that is "afraid of voids" or not. That would mean erasing non-vanishable functions requires an operator: can you write reduce [1 + 2 if null [4]] or do you have to write reduce [1 + 2 ^ if null [4]]

Needing ^ Seems Pointless If REDUCE Is Liberal About SPLICE!

I can see a line of thinking that we'd be looking to maintain the "N expressions in, N values out" rule by default. That might even suggest prohibiting erasure of "obviously" vanishing things like COMMENT (e.g. because you want to process them via predicate to build functions like PACK)

But today's REDUCE handles SPLICE!:

>> reduce [1 + 2 spread [10 20]]
== [3 10 20]

If non-"vanishable" functions that will return an empty SPLICE! will effectively vanish, then I don't see what good we're doing by stopping non-vanishing functions that return VOID! from vanishing.

I feel like "liberal" and "rigid" REDUCE both have their place

It's just not completely clear which should be the default.

Part of me feels like it's a waste of all the safety mechanics to not require you to explicitly mark non-vanishable functions that vanish...

...but I do feel like the liberal REDUCE is the more useful behavior.

Should we perhaps say that REDUCE is rigid by default, and REDUCE* offers you the relaxed mode, and then people can just map REDUCE* to REDUCE if that's their preference?

(I'll point out that the name REDUCE seems a bit misleading, if you can say reduce [x] and have that be [1 2 3 4 5 6] if X is a SPLICE!...it seems more like "expand" than "reduce".)

2 posts were split to a new topic: BLANK! ("comma") Behavior in REDUCE

Perhaps if you want the splicing/vanishing behavior, you should use JOIN BLOCK! instead of REDUCE? It also lets you say what target type you want...

I came up with the idea that you could specify the target datatype as the first argument to JOIN, which gives it more flexibility to be "reduce-like" while still doing other cool things:

>> join 'abc 10 + 20
== abc30

>> join word! ["a" if 1 = 2 ["b"] "c"]
== ac

>> join group! [1 + 2 spread [x y z] if 1 = 2 ["x"]]
== (3 x y z)

Allowing you to splice and opt-out of components makes sense for JOIN-ing.

Maybe REDUCE being tailored specifically to the case of "N expressions => N values" is a good separation, because the word "reduce" seems to apply better to that?

:thinking:

I Think I Like This...

Though join block! is slightly more typing than REDUCE, it's 5 more characters:

 join block! [...]
      reduce [...]
 12345

But really I feel like if you are doing something over and over again to where you care about 5 characters, you should be shorthanding or dialecting. At least make it jblk or something.

Note that JOIN could even offer to make splices...

>> append [a b c] join splice! [1 + 2 10 + 20]
== [a b c 3 30]

That's actually a character shorter than:

>> append [a b c] spread reduce [1 + 2 10 + 20]
== [a b c 3 30]

Compromise: Predicates Return SPLICE!, VOID!, or ~(VETO)~

In thinking about how to "override" the behavior, I wondered why there'd be rules constraining a function passed to REDUCE on its result.

>> reduce:predicate [[1 2] [3 4]] spread/
== [1 2 3 4]

>> reduce:predicate [null 1020 null] opt/
== [1020]

So if the predicate function is given the maximum flexibility, it just means that the default predicate function is what constrains things to one-and-only-one item per expression.

Hence the proposed "REDUCE*" would just be REDUCE with IDENTITY as the predicate.

BUT I think it's better to think of using REDUCE with a predicate as an "implementation tool for other things", because otherwise you might get confused about whether any given "REDUCE" is going to vaporize voids or fold in splices.

So rather than put REDUCE* in the box, it's likely better to guide people toward JOIN and think of REDUCE as "its own thing" that actually sees all the evaluations--even COMMENTs.

1 Like