Here is some pretty innocuous-looking code:
n: 1 for-each item block [ print ["the" item "is" n "in the block"] n: n + 1 ]
But because Rebol runs ACTION!s when they are bound to WORD!s, and you can put ACTION!s in BLOCK!s, you can get into trouble:
muhaha: func ['x 'y 'z] [print ["stealing your args!" x y z]] block: compose [10 (:muhaha) 20]
Enumerating such a thing produces total garbage:
the 10 is 1 in the block stealing your args! is n in the block the #[void] the 20 is 3 in the block == 4
I’m not concerned about malicious cases–Rebol is fundamentally not secure about this kind of thing (see and discuss at “The Philosophy of Security in Rebol”) But you want to write code that gives reasonable error messages, especially when writing a mezzanine routine.
Unfortunately, doing this “the right way” is ugly (and it doesn’t actually work in a general sense):
n: 1 for-each item block [ print ["the" :item "is" n "in the block"] n: n + 1 ]
That only has one reference to the item, but it’s clearly much uglier in real cases. But beyond being ugly, tacking a
: onto the front of everything doesn’t have the same semantics. What if you think you’re dealing with a block of objects, and want to call methods on them?
:item/some-method aren’t the same. (Note: perhaps (:item)/some-method should work?)
Point being: even if you think adding colons is mitigating the problems, you’re not getting what you really want…which most of the time, is an error.
Could we make the common case work better?
Something I’ve often wondered is if these enumerators which take words and set them through a loop would only let you get at ACTION!s if you used some other ANY-WORD! type. This way, they could error otherwise…and common loops could feel safe.
For the sake of example, let’s say it’s a GET-WORD! passed to FOR-EACH if you actually are prepared to work with ACTION!s. The first example above, by using a plain WORD!, would output a clear error instead of gibberish:
the 10 is 1 in the block ** Error: Variable `item` must be GET-WORD! to hold ACTION! in FOR-EACH
If GET-WORD! were used, it would make some amount of sense–and line up with the fact that you would be wanting to use GET-WORD! in the body too. But it sacrifices the current feature for GET-WORD!, which is “soft quoting”…where the word specifies the word to look at to find the word to use:
>> word: 'item >> for-each :word [1 2 3] [print [item]] 1 2 3
However, there’s another way of doing this, with GROUP!:
>> word: 'item >> for-each (word) [1 2 3] [print [item]] 1 2 3
Soft-quoting doesn’t really come up all that terribly often. Still, it’s a little annoying that this would throw a wrench into COMPOSE situations doing simple soft-quotes, but you could attack that multiple ways.
I think it would be much better if you could mark very clearly which enumerations were intentionally working with ACTION!s. The error messages would be better, and people are savvy enough to know there could be a problem won’t be so paranoid in their basic enumerations–knowing the error will be delivered.
Should just loops be affected, or all soft quotes?
It could be loops for starters. They could be switched to hard quotes and do their own logic, only giving soft-quote semantics for GROUP!s.
Or maybe soft-quoting is too sacred as a mechanism in PATH! processing…and you don’t want to have to type foo/(bar) instead of foo/:bar…so that translates to wanting to keep it in sync.
This might mean using another datatype, e.g. for-each @item block […] to say “ACTION!s are okay”.
Any thoughts? I know Rebol has some elements of “it’s a fundamentally unsafe language”, but I just feel there need to be some limits. But I don’t want to bulletproof every FOR-EACH in the system against function injections–even if they are all just accidents, you want better feedback than having a mess be made.