GET+SET vs PICK+POKE - What's The Difference?

Historically, GET could not get a path:

rebol2>> obj: make object! [x: 10]

rebol2>> get 'obj/x
** Script Error: get expected word argument of type: any-word object none

That changed in R3-Alpha. Red followed suit:

r3-alpha/red>> get 'obj/x
== 10

Which seems like an improvement...but opened the door to something I've complained about: GET having side-effects, such as:

red>> path: 'obj/(print "Boo!" 'x)
== obj/(print "Boo!" 'x)

red>> get path
== 10

When you say that two sequential GETs can get something completely different even when nothing has changed, that really pulls the rug out from under any generic code that wants to build upon what a GET is. Similar issues apply to SET.

How Do Pick and Poke Compare?

PICK and POKE add an extra parameter of a location to pick or poke from. But then they still have a "picker" of some kind.

This leads one to wonder if this would work, but it doesn't:

r3-alpha>> outer: make object! [inner: make object! [x: 10]]

r3-alpha>> pick outer 'inner/x
** Script error: pick does not allow object! for its aggregate argument

But there are two possible interpretations. If OUTER is something like a MAP, it could be looking up the PATH! inner/x as the key in the map. Or it could be looking up the key inner, fetching the thing in the map, and then picking x out of that.

MAP!s don't allow PATH! in R3-Alpha or Red, but if they did...we'd assume it would interpret inner/x as the key.

So Historical PICK and POKE are Strictly Less Powerful?

This makes it seem like GET and SET have the ability to do anything that a PATH! or SET-PATH! can do. But PICK and POKE can only go the last mile and ask one container about its response to one key.

Could we make a synonym for PICK, if we just GET a PATH! that we make up? Let's try that in Red:

red>> pick2: func [series index] [
          get make path! reduce ['series to paren! reduce ['quote index]]

red>> m: make map! [a 10 b 20]

red>> pick2 m 'a
== 10

red>> b: [x 30 y 40]

red>> pick2 b 'y
== 40

It appears to work, but the issue is that I'm sure these are completely different code paths. So you'll see subtly different behaviors for PICK vs. pathing where they'll be the same most of the time, but then not.

It would only make sense to have two codebases if someone could articulate what's different about "picking" and "pathing". Outside of function call dispatch with refinements I can't think of a good argument for a difference. And Ren-C uses TUPLE! instead of PATH! for conventional picks, so the tuple-based picking could truly be the same.

Not Easy To Reason About

This is all made-up stuff with really imaginary semantics. And I've come up against the hard questions like trying to make this work:

 item.(expression): default [...]

If you GET that SET-PATH! on the left to check to see if there's a value in it or not, and there isn't, then you decide to run the right hand side. Then you want to SET the SET-PATH! on the left...but without some alternate mechanism, you'll be evaluating the expression twice.

Being able to turn that item.(expression) into some sort of reusable currency that you can GET and SET multiple times without side effects is ideal. Once this was done by COMPOSE'ing that PATH!, but paths are now more restrictive in what they can have as it would have to be turned into a block.

1 Like

...A Grand Unifying Theory??

I think GET, SET, PICK, POKE, PATH!, and SET-PATH! should all be running on the same basic hook. I've explained why that hook being recursive makes sense for POKE.

What if a GET which requests an additional output parameter (e.g. STEPS) is allowed to evaluate groups in PATH!...killing two birds with one stone?

>> obj: make object! [x: 10]

>> tuple: obj.(print "!! SIDE EFFECT !!", 'x)

>> get tuple
** Error: Use GET with STEPS: output to evaluate sequences with GROUP!

>> [result steps]: get tuple
== 10

>> steps
== [obj x]

We have a new option now that GET and SET no longer accept BLOCK! to mean "get a block of variables" or "set a block of variables" (see UNPACK). It means that GET and SET can interpret a block as steps... basically a TUPLE! that has had its group elements processed (and hence may contain elements not typically legal in tuple).

>> get steps
== 10

>> set steps 20
== 20

>> obj.x
== 20

If you want to run a tuple with GROUP!s in it, you can just "opt-in" to the steps without naming them:

>> [# #]: get tuple
== 20

Preliminary Tests Of This Idea Look Promising :star2:

There's still a lot to mull over with this, but it's the cleanest-looking angle I've come up with yet.

Errors are still a bit of a puzzle. Once you've converted a TUPLE!/PATH! into one of these "steps blocks" then you've gone away from the source level of what the user wrote. The later SETs and GETs will only have the block--presumably not the path.

(Note: Though you could save it and pass it in? Maybe the original path could be cached as part of the block, as a kind of commentary used in error delivery?)

In any case, when you use the steps later you have less odds of erroring since the initial path access that returned the steps block worked. We'll have to see how it pans out.