Here's something seemingly-simple that a newcomer to Redbol might attempt:
TASK: Write a function TWOSIE? that...
- returns true if a value is the INTEGER! of 2, or a BLOCK! of length 2
- returns false otherwise
twosie?: func [x] [ ; Ren-C calls this LAMBDA, due to no RETURN
to-logic any [
x = 2 ; will also pick up DECIMAL!, since 2 = 2.0
all [
block? x
2 = length? x ; length at current position, not total
]
]
]
An "experienced" Redbol user would point out what happens when you pass a FUNCTION!.
red>> twosie? :append
*** Script Error: = operator is missing an argument
*** Where: =
*** Stack: twosie? to-logic
If x turns out to be a FUNCTION!, it is invoked by the references inside the body. Which can cause unwanted side effects, as well as arbitrarily cryptic errors.
How Was This Mitigated?
You could restrict the set of accepted types, and exclude functions. But if you make it only accept INTEGER! and BLOCK! that undermines the aspect that the function's job is to do testing.
Doing it "right" is annoying, putting colons on every access...possibly omitting some when the value is in a context where the program logic would say it can't be a function at that point.
twosie?: func [x] [
to-logic any [
:x = 2
all [
block? :x
2 = length? :x ; :x is optional, as known to be a BLOCK! here
]
]
]
Sometimes, people would minimize the number of GET-WORD!s needed by short-circuiting a test for FUNCTION! first:
twosie?: func [x] [
if function? :x [return false]
to-logic any [
x = 2
all [
block? x
2 = length? x
]
]
]
This is fairly unsatisfying as well. It breaks easily if someone reorganizes the code and doesn't realize the test has to come first, or if there has to be additional handling and skipping all the code that uses the variable isn't the desired semantic.
This situation is a tax on users, who are continuously torn between writing obvious code that is broken... vs. cluttered code that winds up being made more brittle due to the maintenance of the clutter.
It would clearly be ideal if the obvious code was also correct.
The Nuanced Compromise Of Isotopic ACTION!s
Something that occurred to me was to ask what would happen if there were two kinds of actions:
-
ACTION! isotopes, which would run if they were referenced via a WORD! or TUPLE!
-
Plain ACTION!s, which would be inert when accessed by WORD!
An obvious good part of this idea would be that a "normal" argument to a function would never be able to be an isotope (solving the problems outlined above).
An obvious questionable part of this idea is introducing another state to worry about.
I've implemented it--though it is a radical change affecting kind of everything. :-/ There are certainly a lot of questions raised and details to come up.
Something to realize is that there's a fundamental complexity coming from the fact that Rebol wants WORD! references to execute actions automatically much of the time. But you still have a lot of places that want to talk about values "as-is". We cannot "wish away" that complexity...only reshape it.
But I think the ability to have obviously-written code in cases like TWOSIE? tips the balance. I don't know that meta-code gets truly any harder to write, it just gets different...while the simple examples work without GET-WORD!s.
I'll use this thread to document differences to know about.