GET-BLOCK!, GET, and REDUCE

R3-Alpha had what I had thought was a bizarre behavior:

r3-alpha>> get 5 + 5
== 10

If something wasn't a WORD! or PATH! it would fall through. Unless it was an OBJECT!, in which case it would get the values of the object vs. the OBJECT! itself.

Then it could SET a block, but it didn't symmetrically GET one...the BLOCK! was just one of the miscellaneous things that fell out:

 r3-alpha>> set [x y] [10 20]
 == [10 20]

 r3-alpha>> reduce [x y]
 == [10 20]

 r3-alpha>> get [x y]
 == [x y]  ; one of the as-is types, Red/Rebol2 error on this

I realized that if GET of a BLOCK! worked, then the GET of an OBJECT! behavior could just be get words of obj, which seemed more clear and generalized. So I changed GET of a BLOCK! to work symmetrically to set.

 ren-c>> x: 10

 ren-c>> y: 20

 ren-c>> get [x y]
 == [10 20]

And then along came GET-BLOCK! and SET-BLOCK!

The most sensible seeming behavior for :[...whatever...] would seem to be the same as get [...whatever...]. So at first I decided a shorthand for REDUCE was likely more useful. It seems you want to REDUCE blocks more often than you GET them (which is especially true since Redbol couldn't GET blocks at all).

But this raised a question: should GET of a BLOCK! reduce it? If R3-Alpha's odd behavior of letting you GET inert quantities was preserved you'd be almost there.

r3-alpha>> get 5 + 5
== 10  ; the weird behavior mentioned before

>> get [x y 5 + 5]
== [10 20 10]  ; potential extension of this

On the surface this would seem to offer an advantage of a shorter word than REDUCE, along with a symmetry between GET-BLOCK! and GET of a BLOCK!. But the symmetries break down because you expect GET of a WORD! that looks up to a function to give you the function back by value and not apply it. :frowning:

>> get 'add
== #[action! ...]

>> :add
== #[action]

>> get [add]
** Error: ADD is missing its value1 argument   ; uh oh

This was the reason that GET-BLOCK! behavior was changed away from REDUCE. Which I forgot when writing up my idea last night.

Alternate ideas?

Even if GET of a word looking up to an ACTION! gives an action directly, one could allow GET of a GROUP!:

 >> get '(add 1 2)
 == 3

 >> get [add (add 1 2)]
 == [#[action! ...] 3]

This could mean a GET-BLOCK! could be almost as powerful as REDUCE. It's just that REDUCE wouldn't need parentheses around function calls. But having something that's almost-reduce-but-not-quite might just be asking for trouble, and having the weird get 5 + 5 working could just be a slippery slope of luring people into misunderstandings.

I think it's kind of non-negotiable psychologically for GET of a BLOCK! to do the same thing as a GET-BLOCK!. Allowing that to be REDUCE seems to pack some power, but it would mess with things like:

set words of obj1 get words of obj2

If any of those words were functions, you'd wind up running them.

Hmmm.

It turns out it hadn't actually been changed yet, just written up that it should be changed to match GET of a BLOCK!.

But I'm questioning this assumption. Having :[x + 1 y * 2] act as reduce [x + 1 y * 2] seems much more useful.

I'm not sure how to reconcile this with not wanting GET of a BLOCK! to be a REDUCE. :-/ Rebol2, R3-Alpha, and Red all have no behavior for trying to get a block...it's an error. Maybe it's worth it to keep it that way so that there isn't a flagrant inconsistency?

I've also suggested higher purposes in the evaluator for SET-BLOCK!, for multiple return values. Hmm.

1 Like

My "suggested higher purposes" won out, and I'm pretty psyched about it.

To me this clinches the idea that :[what ever] does not need to mean the same thing as get [what ever] (if that means anything at all). As with the idea of doing element-wise assignment via something other than SET (e.g. ASSIGN), there could be some other analogous thing. (It's kind of like a form of REDUCE that just refuses to run functions... a kind of REDUCE/ONLY or REDUCE/SAFE?)

In fact... what if the old notion of "GET BLOCK!" is instead just a parameterized COPY that does an GET on each position? Avoiding the fact that there's no good syntax for "predicates" yet, imagine being able to provide an arbitrary function that transforms each item during a COPY?

 >> copy /negate [1 -2]
 == [-1 2]

 >> x: 10
 >> y: 20

 >> copy /get [x y]
 == [10 20]

Though you can imagine predicates that wanted to operate by position ("like a FORALL") as opposed to one item at a time ("like a FOREACH"). Perhaps that could be the difference between COPY and REDUCE, in that REDUCE has the ability to give you a smaller set of things than you started with?

>> reduce /(p => [p/1 > p/2]) [1 2 4 3]  ; 4 items
== [#[true] #[false]]  ; 2 items

Regardless of the specifics of these decisions I think the win of having :[...] mean REDUCE is very high. Having accepted multiple return values for [...]: instead of member-wise assignment clears up the concern I mention in this thread; it's not worth worrying about, there are better answers.

The most useful variant of REDUCE for this meaning is likely one where NULLs vanish, as opposed to turning into BLANK! or VOID! in order to not have extra slots. Getting the other variation might be reduce /try [...]


So long as we are on the subject, think about how you'd feel if code said copy .get [x y] or reduce .try [...] instead. Too subtle, or "just right?"

1 Like

A post was merged into an existing topic: Dots and Slashes: The Hyphen and Colon Disruption

I have restored the behavior that GET-BLOCK! acts as REDUCE

>> :[1 + 2 10 + 20]
== [3 30]

This means that if you're dealing with a literal block, you can avoid functions like REPEND.

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

And I think it means we can be happy enough defining JOIN as not reducing by default:

>> data: [a b c]

>> join data [1 + 2 10 + 20]
== [a b c 1 + 2 10 + 20]

>> data
== [a b c]

If you want to reduce, you ask for it:

>> data: [a b c]

>> join data :[1 + 2 10 + 20]
== [a b c 3 30]

>> data
== [a b c]

This case of literal information actually comes up rather often. For those who prefer writing out the word REDUCE even in literal cases, no problem.

What about SET and GET natives and BLOCK?!

The thing I had been fretting about was if SET of BLOCK! and a GET of BLOCK! didn't do the same thing as a SET-BLOCK! and GET-BLOCK! would.

Yet today know that SET-BLOCK! has the behavior of multi-return, and that's been a huge win. It's not possible to unify that behavior for what it means to combine the SET native and a BLOCK! value.

And as I've mentioned, historical Rebol2 and Red do not have meaning for a GET of a BLOCK!, it's an error:

red>> a: 10
red>> b: 20
red>> get [a b]
*** Script Error: get does not allow block! for its word argument

So it's not [10 20] as one might expect (though you might argue it's just an oversight and they'll add it some day).

But I don't like these operations and I've explained why: not all variable states can be put into block elements. You can't put NULL and you can't put BAD-WORD! isotopes.

The workaround I came up with is called UNPACK and I think it really sorts out the various problems that GET and SET of BLOCK!s invariably introduce.

2 Likes

And the best keeps getting better:

>> if true :[elide print "YES" 1 + 2 3 + 4] else :[elide print "NO" 10 + 20]
YES
== [3 7]

So GET-BLOCK! branches now reduce. And they only do the reduce if the branch should be taken.

if condition :[...] <=> if condition [reduce [...]]

A small but nice convenience! If you have a bit of material you may or may not want to splice in a COMPOSE, for example:

>> obj: make object! [word1: 'orange, word2: 'pear]

>> compose [apple banana ((if obj :[obj.word1 obj.word2]))]
== [apple banana orange pear]

(Note: I still prefer non-splicing as the default for COMPOSE, and I feel the (( )) helps to hint at the potential for multiple splices in a visually suggestive way. The only thing that has me a bit bummed is the casualness with which NULL is tolerated for vaporization when it errors other places. Both plain ( ) and (( )) allow the composed clause to vaporize via NULL...still turning this around in my head a bit. I'm wondering if you might have to use (( )) if you want things to vaporize, to suggest the number of things spliced is flexible...not just potentially many, but also potentially 0?)

1 Like

Leading colon should likely make the block inert now, that :FOO is going to be the new "refinement".

If this feature is still interesting (and I think it is), probably leading slash should reduce the block.

>> if true /[elide print "YES" 1 + 2 3 + 4] else /[elide print "NO" 10 + 20]
YES
== [3 7]

It's weird, but no more weird than GET-BLOCK! was. And it kind of makes sense? :thinking: You're asking the BLOCK! to "run" in a sense, like a leading slash would do for an inert FRAME!. Though it's running with REDUCE and not EVAL.