GET-WORD! for loop vars / function args mean "Allow ACTION!s"?

I feel comfortable with the step ahead (well, technically step back to Rebol2 semantics) on GET-WORD! of a VOID! raising errors. So GET-WORD! really is dedicated to the defusing of ACTION!s so that they do not run.

With that hardened association, it makes me wonder if a related concept might make sense...which is to cue people to notice when a parameter or loop variable is allowed to be an ACTION! by its declaration:

This would let people be less paranoid about writing their routines, keeping them from obfuscating it with protections in the common case. Let's take an example:

>> foo: func [] [print "Formatting hard drive...."]

; In today's world...

>> print-val: func [x] [print ["The value is" x]]
>> print-val :foo
The value is Formatting hard drive...

Imagine if plain WORD! arguments prohibited ACTION! (even if in the typeset, as per ANY-TYPE!), but GET-WORD! arguments allowed it:

; The proposed world...

>> print-val: func [x] [print ["The value is" x]]
>> print-val :foo 
** Error: Function assigned to X, to allow this name the token :X

>> print-val: func [:x] [print ["The value is" x]]
>> print-val :foo 
The value is Formatting hard drive...

Yet the real point is what you get when you can scan the second example and notice the asymmetry. You've been given a short visual clue that might hint you into the behavior of mirroring the spec. If you see print-val: func [:x] [print ["The value is" x]] and then notice the disparity of the parameter being named :x and invoked as x, you might question why that's not :x to match.

It seems to me that every little bit of such cue-ing can help. I'm imagining this applying in loops as well:

>> block: compose [1 (:foo) 2]
>> for-each var block [print ["The value is" var]]
The value is 1
** Error: Function assigned to VAR, to allow this name the token :VAR

>> block: compose [1 (:foo) 2]
>> for-each :var block [print ["The value is" var]]
The value is 1
The value is Formatting hard drive...

The value is 2

Again, seeing the colon on the variable helps hint what's going on. And basic code doing loops that doesn't expect to be finding ACTION!s in a block will be somewhat protected.

This would need new notation for "hard literal parameters"

Today's GET-WORD! is an "unescapable" literal argument. So even if the callsite is a GROUP!, the argument receives the group vs. the evaluation product.

I've in the past suggested func ['(arg)] [...] as a more semiotic way of denoting "escapable literal parameters"...and then func ['arg] [...] could be unescapable. It looks nice and kind of shows you the idea that "it's quoted, but a GROUP! can get you past that". Also you can mix it with the colon cases, for func [':arg] [...] or func ['(:arg)] [...] to say that even though you're quoting, you still consider ACTION! a candidate (if found literally in the block or evaluated to by a soft literal situation).

On the downside, you really want people to use hard literal parameters as a last resort. So this makes it easy to forget the more relaxed form as the better default choice.

Only GROUP! could do escaping for loop parameters

Historically for-each :named-var [...] [...] was a shorthand for-each (named-var) [...] [...]. This would be reclaiming those GET-WORD! and GET-PATH! cases for the "I can process actions in the loop body" signal.

Honestly, needing to escape via an already named variable doesn't come up all that often. One case I mentioned was the idea of reusing a variable that already exists (to save on creating an OBJECT! for the iteration), but that could be done with @word or 'word perhaps.

What do people think?

I know this isn't the end-all and be-all of somehow making the language safe. Once you start picking things out of objects, anything you thought could have been just data could actually be active.

But I think it would help ease the pain of perfectionists who want to write "correct" code, to take it easy and think "well I don't have to handle these dangerous cases, because the proper errors will happen for me just by not using GET-WORD!".

This has been on my mind a long time (see Dec '18 post). Because I'm one of those people who tries to write generic code and frets about what would happen on the day that VOID!s or ACTION!s come I'm caught between the balance of feeling negligent by not peppering with checks, or junking up otherwise elegant code for the sake of something that could happen.

I feel like it may be time for it, now that we're committing to the GET-WORD! really meaning only "defuse action" and not "get void" (disallowed) or "get null" (always allowed, even with WORD!). That shores up the bit about VOID!s not being able to run amok. This change would be a nice way of complementing that by stopping ACTION!s from throwing a wrench into things--while keeping the common case code clean.

1 Like

Just a note that this has to also account for optional arguments ("refinements"), and there should be a canon representation.

How many ways might you say "literal escapable optional argument that can be an action"?

  1. func ['(:/param)] [...]
  2. func [':(/param)] [...]
  3. func [:/('param)] [...]
  4. func ['/(:param)] [...]

All are legal, but I feel like only one representation should be allowed. I like option 1 because it puts the escapability right inside the quote. I like option 4 because it puts the "actions are legal" directly on the arg. (Note that func ['(/:param)] [...] isn't legal because colons are not allowed interior to path elements.)

I think 1 wins, just because of preserving the junction of the quoting with the escapability.

For comparison, here's soft literal optional, default not permitting ACTION!

not-any-function!: make typeset! [... everything but any-function! ...]
func [/param 'arg [not-any-function!]] [...]  ; historical

func ['(/param)] [...]  ; proposed

Soft literal optional, permitting action

func [/param 'arg] [...]  ; historical

func ['(:/param)] [...]  ; proposed

Hard literal optional, default not permitting ACTION!

not-any-function!: make typeset! [... everything but any-function! ...]
func [/param :arg [not-any-function!]] [...]  ; historical

func ['/param] [...]  ; proposed

Hard literal optional, permitting ACTION!

func [/param :arg] [...]  ; historical

func [':/param] [...]  ; proposed

I know it's a little dense, but it seems to me that the semiotics are there.

  • Apostrophe means quote, don't evaluate
  • GROUP! immediately inside apostrophe means escapable quote, use GROUP! to subvert
  • Colon means ACTION!s allowed (you likely want to use GET-WORD!s inside the function with their own colons)
  • Slash means optional, like a refinement.

I haven't actually seen a whole lot of quoted refinements, ever. So the most common weird thing you'd see would be :/param, a refinement that accepts ACTION! (like a comparison function for sorts, or a function for filling in elements for ARRAY/INITIAL).

While this might seem overly symbolic, function specs are a bit tough since they try to be full spectrum in permitting WORD!s, which means they've taken all the words away. We can't do func [quoted optional escapable x] [...] Because that would be 4 parameters. func ['/(x)] [...] is compact and--I think--learnable.

1 Like