The Grand Leading-Slash "Safety, or Burden?" Question

Overall, I have been tremendously happy with how the ideas of the Big Alien Proposal have worked out.

That started from the concept that when slashes appear, they either come before a function they run, or after a function they suppress execution for.

foo.bar
; ^-- foo is an entity from which BAR is being selected.  BAR is not
; allowed to be an antiform frame, so this syntax cannot invoke a
; function call (though it can invoke an 'accessor', e.g. a "getter"
; which is 0-arity).

foo/bar
; ^-- foo is an entity from which BAR (a FRAME! or antiform FRAME!)
; is being selected and then invoked.  This will generate an error if
; bar is not a frame or antiform frame.

foo.bar/
; ^-- bar is a field which is an antiform FRAME!, whose execution is
; being suppressed.  This expression will return an antiform frame, or
; an error if not an antiform frame.

foo
; ^-- conventional WORD! reference, will run an antiform frame as an
; action invocation or fetch other values as-is

/foo
; ^-- invocation reference, will run an antiform frame (or plain frame)
; as an action invocation and give errors on other types

foo/
; ^-- action suppression, will give you back an antiform frame as-is
; and error on other types.

(If you're curious about why /foo will run plain FRAME! as well as antiform, while foo/ will not return an antiform frame for plain FRAME!, this is based on the idea that it's better to be conservative when fetching values so that you won't get surprised by getting a plain frame back from ^foo which gives everything back as-is.)


I've written elsewhere how pleased I am that the way you suppress a function's execution is by throwing up a "barrier" with a separating slash that makes it clear arguments are not being gathered at the callsite. That's really slick.

For this idea to work, something else had to be used for refinements. That meant invention of the CHAIN! datatype has opened a lot of interesting doors, and I find it's quite learnable to see things like trim:auto:tail instead of trim/auto/tail.

I actually prefer it! What some might think of a disadvantage of being "less noticeable" turns into an advantage... trim:auto really could have been a function called trim-auto just as easily. Why would you want a slash to make the fact that it has a refinement "pop"? The slashes to make function calls or suppression pop are much better applied.

So that's all good. :smile_cat: No regrets!

But... requiring /FOO: For Action Assigns Hasn't Gel'd

Another part of the proposal was that in order to get tighter control on what was a function or not, you would be required to assign functions using a leading-slash kind of SET-WORD!.

>> foo: func [a b] [return a + b]
** Error: FOO: can't be used to assign antiform FRAME!, use /FOO:

>> /foo: func [a b] [return a + b]
== ~#[frame! "foo" [a b]]]~  ; anti

It hasn't settled with me after working with it for some time.

As I mentioned above, colons for refinements was easy to adapt to...and now that I'm adapted, I prefer it. But I'm kind of cursing under my breath the thought of having typed test: and having to backspace over it so it says /test:. And then I go "hrmph."

It's certainly nice to have a shorthand for foo: ensure action! ... and so I don't think the feature should be thrown out. It helps make things clearer to say "this is an action assignment" in a nice and light way.

But when you're writing foo: func [...] [...] you already know it's an action, and being forced to say you do makes the code look junky. It's a burden in a way the other changes are not. It's the only change that increases the character count.

If it can't settle with me, it will definitely put others off, and lose a major aspect of the clean feeling of the language.

What's At Stake By Not Enforcing This?

Ren-C has a powerful story about how antiforms can't be put in blocks, which means you can write this kind of code and it "just works":

block2: collect [
    for-each 'item block1 [keep item]
]

assert [equal? block1 block2]

When you compare it to Rebol2/R3-Alpha/Red, it's one of those vastly superior situations. You aren't getting tricked into receiving an ITEM in the FOR-EACH that would generate an unset variable error, or conflate with the state that gets returned when an item can't be picked from a block, or accidentally run a function. It's a solid solution.

But that's only for blocks. What about other places, like objects?

If we don't put barriers on how action antiforms get assigned to variables, we get the problem all over again:

for-each [key value] obj [
    if integer? value [  ; oops, what if VALUE is an action antiform!
        print "Found an integer"
    ]
]

There's no way in this case to say "variables can't hold antiforms". Logic is an antiform. Words holding antiform frames are actions.

Getting this under control with slashes is the kind of thing I've been trying to do for a long time, I've just never had the syntax. Leading slashes felt like it could be the key:

for-each [key value] obj [...]  ; value can't be frame antiform

for-each [key /value] obj [...]  ; value must be frame antiform

for-each [key ^value] obj [...]  ; value may be anything whatsoever

But if these rules are applied everywhere, what you have to do gets more complex:

set $x does [print "Is this an error?"]

set $/x does [print "Do you have to do this?"]

>> var: $x
== x  ; bound

set var does [print "If this errors, how to make VAR into bound /x?"]

set:active var does [print "Do you use refinements?"] (or just SET:ANY ?)

Nothing is free. And the already more complicated world where x: is a CHAIN! instead of a fundamental different type of word has its own issues, that these all pile on top of.

2 Likes

Maybe there are some functions that are marked as "generators", and others that are not... such that if you are receiving an antiform action back from a non-generator, then that's when you need the slash?

foo: func [...] [...]  ; generator, no slash needed

/thing: select obj item  ; SELECT not a generator, known to be function

^thing: select obj item  ; SELECT not a generator, maybe function

It would be much rarer to see the slashes in these cases, but they would be the actual meaningful places to see them.

This could be handled by a "hot potato" bit on values.

Maybe the way you are considered a hot potato is if you don't explicitly mention ACTION! in the return spec. e.g. if you just say you return [any-value?] then the return of an antiform frame will require the slash. But if you say [any-value? action!] then you're exempt. And if you just say [action!] then you're exempt of course.

Kind of an interesting compromise idea, although most hidden-bit ideas have been scrapped. It's almost like there would be two FRAME! antiforms... a stable one and an unstable one... and generators would return stable forms but non-generators unstable ones, that error unless they find an "aware" receiving site. :thinking: It's just an inkling of an idea at the moment.

The "surprising action" bit works great... when it works. However, as I predicted... being a hidden/special bit means that if you're building something that abstracts assignments, you lose the bit... and it starts to break down.

For instance, consider the GATHER + EMIT functionality in PARSE

 parse [double [x * 2] plus-one [x + 1] [
     gather [some [
         let name: word!, let code: block!
         emit (name): (func [x [integer!]] code)
     ]]
 ]

This is a simple example that performs the equivalent of:

make object! [
    double: func [x [integer!]] [x * 2]
    plus-one: func [x [integer!]] [x + 1]
]

But it does this programmatically. How it does it is by collecting the names of the fields and then the LIFT'ed form of their arguments... so for ACTION! that would be a QUASIFORM!.

This leads us to ask whether a QUASI-FRAME! evaluating and being assigned to a WORD! should count in terms of the hot potato bit. This is a bit of a head-scratcher, as it can't be both.

Could PACK! Be The Hot Potato Bit?

When other parts of the system have faced problems with transient properties that need to decay, they've leaned on PACK!.

So I mused about a concept: what if things that wanted to be generators would pack up lone ACTION! values...

>> lambda [x] [x]
== \~(~&[frame!...]~)~\   ; action antifrom in a pack!

>> foo: lambda [x] [x]   ; works

>> ^foo
== \~&[frame!...]~\  ; action antiform

>> bar: ^foo
** PANIC: "surprising" action assignment (plain action, not in a pack)

This is... actually sort of compelling. It makes the function generators the odd ducks.

If the mechanic were this overt, then I don't think that it should be any "invisible magic" (like detecting that a function only returns ACTION!, and making it return an ACTION! in a PACK!.) Instead, when you write a function generator, you'd just return a function in a pack.

But what it would imply would be that terminal slashes would give you back a function in a pack, otherwise you couldn't do:

my-append: append/

Conveniently, if something like APPLY took an ACTION! as an argument, then apply append/ would decay that action-in-a-pack to a plain ACTION!.

Where this starts to get a bit hazy is what happens if you write:

^foo: lambda [x] [x]

By the rules of ^META, that's going to put a PACK! containing an action into foo.

But this is what happens whenever you write packs into ^META variables. e.g. I will point out that by the rules of ^META, the following puts a PACK! containing a null into foo... not null:

^foo: if okay [null]  ; compare with (foo: if okay [null])

Just how nasty is this? Well, we seem to have been getting away with it so far. The physics inspirations of isotopes sort of speak to the "law of chemistry" in effect here. If you've got a milieu of isotopes out there in the mix, and it only matters "when it matters", it puts the burden on those with more stringent needs to articulate and filter when they care.

It's considerably less nasty--I think--to use PACK! for this than to have a hidden bit. And the idea of having the pack be what "boils away" as the transient evaluation--allowing things like SELECT to return their "pure" answers which have the more "ornery" behavior--seems like it might work.

I'll give this a shot, and see how it goes.

3 posts were split to a new topic: Parallels Between Surprising GHOST! And ACTION!

2 posts were split to a new topic: The ACTION!-PACK!ed Solution To Safe Assignments