Make Your Own Safety?

At times it seems like Rebol can't decide if it's an extremely high-level language, or some kind of assembly language. It's subject to interpretation (pun possibly intended).

Certain unchecked assumptions seem like disasters ready to happen. For instance, if GET of a TUPLE! allows evaluation:

>> o: make object! [x: 10 y: 20]

>> value: $o.(print "Boo!", either condition 'x 'y, elide condition: false)
>> condition: true

>> get value
Boo!
== 10

>> get value
Boo!
== 20

The word GET does not really seem like it would have side-effects. You may not think to check that value is a TUPLE!. You may expect two GETs in a row to return the same thing. Etc. etc.

But if you put code as a TUPLE! in source, it wouldn't seem so uncomfortable:

>> o.(print "Boo!" 'x)
Boo!  ; Well, I told it to say that at this exact callsite, must have meant it...
== 10  

Ren-C has tried to straddle the line, by allowing it in the TUPLE! and SET-TUPLE!, but not in the GET and SET native operations. But this starts creating a drift to where GET of a TUPLE! is not the same as a TUPLE!. That gets tangled up pretty fast...refinements here don't line up with choices there, it feels asymmetrical and unkempt.

Is it worth the tangle? Isn't everyone just one step away from assuming that do compose [(value): ...] will work the same as if you said SET VALUE? Is there really such a difference between the "English" get $foo and foo in the first place?

Rebol is for customization, should people build their own safety?

One hates to pass the buck and say "well, the user can do it". But if you're making a system that's small enough to Put The Personal Back Into Personal Computing, maybe you don't want to second guess things like what everyone would want from SET. You might guess wrong.

Ren-C's pursuit is Power to the People to address the pain points that specifically peeve them. They don't have to wait for any language implementer to do it. e.g.

set: adapt set/ [
    if any-sequence? target [
        for-each 'x target [if group? x [fail "GROUP! in ANY-PATH!"]]
    ]
    ; fall through to normal SET behavior
]

The SET as used by the mezzanine will keep on working. The goal is that this definition applies in whatever place you were doing your work.

They didn't have to redefine the function interface, or rewrite the HELP unless they want to. This is the essence of what we're going for. And there are ways to make it more efficient, you can implement that check as native code against the internal API if you wanted.

So is the main value consistent behavior between XXX and GET $XXX? Then have a GET:HARD which doesn't evaluate groups and uses them as data (e.g. set:hard $my-map.(1 + 2) <sum> would actually consider the GROUP! (1 + 2) to be the key. There are other reasons this ability is important besides maps, like avoiding double-evaluations in constructs like DEFAULT that have to both GET a left hand path and SET it, and don't want to run0 groups twice.)

So in the end, what should SET do on PATH!s with GROUP!s?

I actually was going to go ahead and bias it so that SET goes ahead and runs GROUP!s in the path. Looking at the reality of the code is giving me some cold feet. As is often the case, the process of trying to reverse a change that was put in for a reason is a reminder of the motivations.

I'm going to keep thinking about it. But still, the point I raise here is a valid one. There may be a general principle that we be very selective about where we make our safety pushes--being mindful of the question of how difficult it would be for a user to customize the feature for themselves. The harder it would be, the more attention that issue should get.

1 Like

It's been a while since I've talked about the pain-point of GROUP! evaluations being done automatically by GET:

To try and make situations like this be more intentional, I'd been saying you'd get an error with normal GET, and would have to use GET:GROUPS. So this would play out like:

>> get value
** Error: use GET:GROUPS to allow evaluations in sequences

>> get:groups value
Boo!
== 10

>> get:groups value
Boo!
== 20

I thought I was happy with that. But there are epicycles of the problem.

For instance: UNSET? was implemented as a usermode function:

unset?: func [
    "Determine if a variable looks up to a `~` antiform"
    return: [logic?]
    var [word! path! tuple!]
][
    return nothing? get:any var
]

And then, I had some reasonable-looking code like this:

for-each 'key f [
    if unset? $f.(key) [
        f.(key): value
        break
    ]
]

I'm considering resurrecting the non-GROUP! evaluative way of saying this, as $f.@key although that's perhaps uglier than the historical 'f.:key (beauty is in the eye of the beholder, I guess...)

But does this mean you should have to say UNSET?:GROUPS... e.g. any operation that builds on top of GET-ting needs to expose the :GROUPS switch? :pouting_cat:

It's easier to do this if UNSET? itself was implemented differently:

 unset?: cascade [get:any/ nothing?/]

More briefly:

 unset?: nothing?/get:any/

This would automatically expose the :GROUPS switch.

:GROUPS Has Generally Been An Improvement...

While this shows an awkward situation, I do feel that by and large the :GROUPS switch has been an improvement in terms of helping keep things under control.

It's also now tougher to implement yourself than it was. When there was only PATH! you could look for the groups, but now you've got the potential of a TUPLE! inside a CHAIN! inside a PATH!...so it would be a deep walk to vet a generic target of GET for evaluations.