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.

A Remaining Question: Should NULL Vaporize in REDUCE ?

An interesting thing about the mechanics of UNPACK is that it uses REDUCE-EACH, and not REDUCE. This means it doesn't get bit by being unable to represent the states it wants to assign in a block because it never generates the block of fetches...it just assigns as it goes.

So does that mean we're free of worry? Are all remaining REDUCE cases non-positional?

I think there needs to be more data on this. Certainly SET was the largest application, but we should keep our eyes open.

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

Maybe... not so bizarre?

I have proposed that our historical desire to ascribe a truthy and falsey state to values may be misguided, and in fact all ANY-VALUE! are truthy... with NULL as falsey. With a little magic from ~true~ and ~false~ isotopic WORD!s, it can fill in the blanks with an additional "LOGIC!" concept...that is out-of-band from what can be found in blocks.

With this twist, it would mean that if you wanted to speak about testing values for their truthy/falseyness you wouldn't PICK or SELECT out of a block and test directly...but first go through a GET:

>> if select [alpha: true beta: _] 'beta [
       print "The ANY-VALUE! of BLANK! would be truthy"
   ]
The ANY-VALUE! of BLANK! would be truthy

>> if get select [alpha: true beta: _] 'beta [
       print "GET of BLANK! could return null"
   ]
; void

It suggests this GET was what was missing all along. However, it raises some issues about the semantics of that GET...for instance, if you pass it a QUOTED! you probably want the thing unquoted, which R3-Alpha did not do:

r3-alpha>> x: 10 
== 10

r3-alpha>> get select [alpha: true beta: 'x] 'beta
== 10  ; should "getting a value" imply a REDUCE-like step?

I don't think it should be evaluative, e.g. whatever this mode is doesn't run functions. And presumably you'd have an error if it was a SET-WORD! or similar.

Does this suggest a difference between GET ANY-VALUE! and perhaps LOOKUP of a WORD! variable?

If you really are just trying to test for blankness or non-blankness, I feel like that should be what you say:

>> if not blank? select [alpha: true beta: _] 'beta [
       print "This would not run."
   ]
; void

Anyway, I feel like the burden of interpreting these values should be upon the dialectee, because it seems there's no unified right answer. From the system's perspective of "somethingness" or "nothingness" it makes the most sense to just say IF reacts to NULL and isotopes, because that enables actual features that work... vs. half-baked things that try to solve impossible problems and don't meet the needs half the time.