After debating the topic for some time, I decided FENCE! should indeed be evaluative.
But rather than setting what it does in stone... let's imagine that what FENCE! does when it evaluates is actually to pass the fence to a function you can redefine in the environment.
demo: func [] [
let fence!-EVAL: func [fence] [
print "I got a fence of length" length of fence
return try second fence
]
return {a b c}
]
>> fence!-EVAL: identity/ ; inside DEMO has a LET of its own choice
>> demo
I got a fence of length 3
== b
>> {x: 10 y: 20}
== {x: 10 y: 20} ; ...!
By allowing FENCE! to be evaluative, you can even allow it to be unevaluative if you choose, in a context:
That's an idea so powerful...that having block!-EVAL
, group!-EVAL
, etc. functions all being looked up and run seems like it needs to be done right now. Whatever optimizations are needed to make it not slow in the general non-overridden case can be attended to.
Since FENCE! doesn't do anything at all yet, it's a perfect guinea pig for the technique.
The Default Evaluation Should Be Likely Be Dialected "MAKE"
I think I like the idea that CONSTRUCT is actually is what FENCE! does by default, and you can direct it to make something that's not an object with some special notation (perhaps just something that looks up to a DATATYPE! in the first slot?)
>> string: "0201"
>> {integer! reverse string}
== 1020
I've proposed this before. But that was before pure virtual binding. Now the parts are here, and it's right within reach.
It's time to go for it, because this is what the whole thing is supposed to be about.
20 Minutes Later...
It works.
>> {x: 10 y: 20}
== #[object! [
x: 10
y: 20
]]
>> fence!-EVAL: func [f] [print ["Length is" length of f]]
>> {x: 10 y: 20}
Length is 4
>> fence!-EVAL: construct/
>> {x: 10 y: 20}
== #[object! [
x: 10
y: 20
]]
Years to ponder, minutes to implement. (Of course, it hinges on the blood sweat and tears of pure virtual binding.)
Despite being extensible... IT'S FASTER THAN MAKE OBJECT! because instead of looking up MAKE and looking up OBJECT! and building a frame and all that, it calls a native arity-1 intrinsically, with no frame at all! So you're paying for fewer word lookups and not even making a frame the resulting function (if you are using an intrinsic, which CONSTRUCT is, and presumably whatever other default maker would be too).
But crucially here...you should always have fallbacks for doing the creation without needing to use the lexical form. So you can use FENCE! creatively, however you like... but still have MAKE OBJECT! (or whatever) passed blocks to get the behavior if you need it.
And if there's only hookpoints for BLOCK!, FENCE! and GROUP!... but not their variations.. you'll always have quoted '[blocks] and $(groups) etc to fall back on if necessary.
Wild Example #1 : Progressive Parsing
Let's say you wanted to do a parse, but not all at once... rather continuing it a little piece at a time with handling code.
data: [
The Sharp Gray @Fork "Quantum Leaped" Over The Lazy @Red
]
f: lambda [rule [block!]] [
parse data [accept [rule, elide data: <here>]]
]
The short (meaningless) name gets things about as brief as you can get in "historical" code:
designer: f [some word!, one]
assert [designer = @Fork]
occurrence: f [text!, elide 'Over]
assert [occurrence = "Quantum Leaped"]
other: f [collect [
keep one, keep ('Intellectually) keep spread across to <end>
]]
assert [other = [The Intellectually Lazy @Red]]
But what if you wanted to do it so that a FENCE! was an implicit call to the parse steps?
fence!-EVAL: lambda [rule [fence!]] [
rule: as block! rule
parse data [accept [rule, elide data: <here>]]
]
Then your calls could look like this:
designer: {some word!, one}
assert [designer = @Fork]
occurrence: {text!, elide 'Over}
assert [occurrence = "Quantum Leaped"]
other: {collect [
keep one, keep ('Intellectually) keep spread across to <end>
]}
assert [other = [The Intellectually Lazy @Red]]
Yes, it works!
Is It THAT Different Than Using A Function? YES.
The difference is significant, and more than just cosmetic.
We've seen arguments against "more parts" before... e.g. saying that GROUP! is not necessary if you have BLOCK! (or vice-versa) because you can always split your intent up into multiple tokens. But it's very Turing Tar-Pit to say "oh it's all the same"... because it is not the same.
When the FENCE! can encode itself in one value vs. needing a word-and-a-value, you get better compositional properties. You have smoother meta-analysis when building higher level things that the fences are composed into (vs. WORD!+BLOCK!)
I see mountains of potential here.