-- GOOD NEWS -- -- ENFIX HAS BEEN SOLVED --
I know it's been a long road, but it has reached a point where the design is final. I don't say that lightly!
Some aspects people will be very happy about:
- It involves embracing Rebol's historical "one unit of evaluation" on the left hand side as the default for enfixed operations.
- It also involves going back to this rule for comparison operators, so you can say
if not x = y [...]
and that will meanif not (x = y) [...]
. Same for AND and OR, which are still changed to be conditional...but are no longer "weird" in the way that THEN and ELSE are. - There is no more
#tight
parameter convention, and no TIGHTEN operation.
I'm happy about it too, especially that last point!
Note that I was never particularly opposed to having 1 + 2 * 3
be 9 instead of 7, and I preferred the historical not
... if all things were equal. I just didn't want to sacrifice features I knew were cool and important to get those behaviors, if the system could be coherently built another way. Now it has that coherence and happens to pass through that point, while making everything I wanted work.
This is absolutely NOT merely dialing back to the past... it's very futuristic!
If you read the below explanation and don't want to take my word for it, I invite you to try patching what is described below onto R3-Alpha. #goodluckwiththat (!)
The solution deeply leverages modern evaluator mechanics which would have been difficult to even think about before what all the fiddling produced. It isn't sacrificing ELSE, THEN, or (new) ALSO... it leverages their unusual handling by "demoting" ordinary infix operators to their rules on an as-needed basis. Their mechanic is at the heart of the trick...most operations just don't do the trick except as a last resort.
-- THINGS "JUST WORK" --
Remember...no #tight parameters and no TIGHTEN:
>> +: enfix :add
>> *: enfix :multiply
>> 1 + 2 * 3
== 9
A first realization driving the design is that if you make an operator that quotes its left hand side, this changes matters:
defaulter: enfix function [:left right] [
either null? old: get left [set left right] [old]
]
>> x: null
>> x: defaulter 2 * 3
== 6
>> x: defaulter {^-- see, it wasn't `(x: defaulter 2) * 3`}
== 6
What "tight" behavior actually is in the new model is a resolution of a contention between an operator that wants at least one value's worth of evaluated information on its right, and an operator that wants exactly one value's worth of evaluated information on its left. Left-quoting cuts the knot, so we need not see this as (x: defaulter 2) * 3
just because of (1 + 2) * 3
.
The second realization from seeing it as contention resolution is that errors are a great response when you don't know what to do. For instance: if something evaluates its left and right but has more than one argument, hence can't be forced to left completion:
>> arity-three: enfix func [a b c] [-- a b c]
>> 1 arity-three 2 + 3 4
** Script Error: Ambiguous infix expression--use GROUP! to clarify
** Near: [1 arity-three 2 ~~ + 3 4]
>> 1 arity-three (2 + 3) 4
-- a: 1
-- b: 5
-- c: 4
That error won't look familiar to you, but it's the same point that causes this:
>> if true then [<bad>] [print "You can't do this, now"]
** Script Error: Ambiguous infix expression--use GROUP! to clarify
** Near: [if true ~~ then [<bad>] [print "You can't do this, now"]]
>> if (true then [<good>]) [print "This is okay."]
This is okay.
The reason the error is the same is because ordinary enfix operators get "demoted" to deferred ones if there is a point at which the no-lookahead is exercised. They use the same mechanics and code path at that point.
As twist is that an END on the left for an evaluative parameter is considered to not-have-evaluated. That empowers one of my favorite little pet enfix demos to work out of the box:
+: enfix function [left [<end> integer!] right [integer! <...>]] [
if set? 'left [return add left take right]
sum: 0
cycle [
if tail? right [return sum]
sum: add sum take right
]
]
>> 1 + 2 * 3
== 9
>> (+ 2 * 3 4 * 5 multiply 6 7)
== 68 ; e.g. (2 * 3) + (4 * 5) + (multiply 6 7)
If it were forced to quote left to get the second behavior, it couldn't make the first case work. I think this is probably the most common desire for how evaluative ends on the left should work.
And ELSE, THEN, and (new) ALSO are all still around, with their cool features:
case [
1 > 2 [<nope>]
3 < 4 [<yep>]
] then [
print "One of the above matched!"
] else [
print "Neither of the above matched"
]
That prints One of the above matched
. So clearly THEN and ELSE have to see more than just the one block on the left. This mechanic has been tuned and tweaked, to narrowly solve the purpose and avoid creating problems
-- Enfix Covenant Cheat Sheet ---
Remember that there's still no such thing as an enfix ACTION!. OP! does not exist, and SET/ENFIX and the itself-enfix ENFIX operator are as you have come to know. GET-ting an ACTION! will always get you a non-enfix one. All that's the same. But other things are being set in stone now:
-
One evaluative unit will be taken to be the "normal" mode for left enfix. Its predictability is a strength, you want to be able to say
return x and 'y
and not worry what's going to happen, e.g. that become(return x) and 'y
. -
Comparison operators will be restored to historical infix behavior. That means
if not x = y [...]
will be interpreted asif not (x = y) [...]
again, as opposed toif (not x) = y [...]
-
The mode used by THEN, ELSE, and ALSO will thus be the outlier. These will be called deferred enfix, and their rules are being limited to provide support for the specific scenario they want. Notice for instance that
return x then [...]
is illegal now--you need to sayreturn if x [...] then [...]
Being more rigid with this pattern brings more peace of mind to the use of the feature. -
Variadics won't need to distinguish their last argument TAKE to be consistent with non-variadic argument gathering. This is possible due to how deferred enfix was limited to just solve THEN, ELSE, ALSO, and their ilk. It's no longer trying to also solve AND, +, =, or anything like that...hence it can use a less wily process and keep evaluator complexity in check.
-
There's now no such thing as TIGHTEN or a #tight parameter convention. It's gone. Rejoice.
This was critical to get right
It's been tinkered with so much because I think enfix is very, very important.
Infix makes Rebol a notable minority in the family of languages that it belongs to. But it's a big part of how we build grammars in our head; languages that sacrifice that are missing out on a powerful tool. The Rebol2/Red limitations were not going to work for me, I wanted a solution that expanded the space to basically anything that was semantically coherent.
This is what I was looking for...but it takes a lot of evaluator design to pull it off!