Moving Enfixedness Back into the ACTION!

#1

LONG STORY SHORT:

The seemingly-subtle distinction of "enfix is a property of a WORD!, and not of an ACTION!" arose to try and give a uniformity and stability to the interface of ACTION!. You always knew--for instance--that if you said f: function [a [action!]] [...] that the ACTION! parameter would not be enfix...hence you wouldn't have to "fear" that an arbitrary function would try and take parameters from the left and could "call it normally. It meant that + and add both pointed to the same function identity, even though there was still only one ACTION! type.

(This was actually somewhat similar to how OP! worked...because the type byte is a property of the cell, not the native dispatcher.)

The collateral damage of this distinction was that plus: :+ would lose the enfixedness, since there were no "enfix ACTION! values". This created inconvenience, but was made more tolerable with an ever-evolving set of interesting tricks.

Yet the "security" offered by not having to worry about parameters being nfix functions doesn't matter much...since functions can still have other parameter conventions (quoting, etc.) which break their expectations. The only "safe" way to deal with an unknown function is with APPLY (and we'll have much better APPLY behavior soon). If functions are type checked, the same checking for other parameter conventions could apply to enfixedness as well.

So in an environment where "opportunistic enfix" functions exist (like DEFAULT), it seems too brittle to wind up in a situation where assignments like target: :original don't retain whatever the calling behaviors of the original were. And some of the mechanical reasons for favoring the original method don't exist anymore...e.g. it's possible to COPY an ACTION! and have the copy be a lightweight identity that doesn't need to copy the body of non-natives...yet runs just as fast as the original. So it matters less that + and add would be distinct function identities.

It's therefore probably for the best to revert to letting the ACTION! itself carry enfixedness as part of its calling convention. This also enables compatibility in Redbol emulation that wouldn't be possible if this change were kept.


More on the topic...

Pulling together the disparate ANY-FUNCTION! types into the single ACTION! type has been a good change. If we actually had to have a separate datatype for each new implementation mechanic behind a function, the list would be very long now.

(That list would be something like TYPE-CHECKER!, TYPESET-CHECKER!, JS-NATIVE!, TCC-NATIVE!, ONESHOT!, INVISIBLE!...in addition to GENERIC!, ROUTINE!, NATIVE!...)

The one function type that doesn't neatly fold away has always been the tricky OP!... which has evolved into a whole field of "enfix science" (for lack of a better term).

To try and square the circle of truly having only one type of ACTION!, the trick was to say that it wasn't functions that were enfix...it was the association between a WORD! and an action.

This might sound like splitting hairs. But it meant that there weren't "enfix function specs". And it meant that if you used a GET-WORD! you'd always be extracting a prefix action, giving a fairly uniform calling convention.

>> +: enfix :add

>> 1 + 2
== 3

>> plus: :+  ; extracts plain ACTION! (there *are* no "enfix actions")

>> 1 plus 2
** Error: plus is missing its value2 argument

It was a bit odd. But it pushed the complexity around so that at least you never had to ask an ACTION! if it had a "left hand argument". And any action value you passed a function wouldn't be enfix, making it "easier to call with a uniform convention".

That was before <skip>-able arguments...

Skippable arguments have become something of a game changer, making for some "opportunistically enfix" constructs.

But this means you can't always tell just by using a construct if it's enfix or not. If you've been using DEFAULT in CASE and SWITCH, but never seen it pick up a SET-WORD! or SET-PATH! from the left... you might try def: :default and be confused when it starts looking for its left hand side on the right.

So what's happening is that the line between an enfix function and a not enfixed one begin to blur. It's like every function might take arguments from their left, even if you don't particularly think of them as an enfix function.

So...what if all functions were enfix?

As a thought experiment, what if you say everything is enfix, but the left hand side may be skippable? The convention would then be that you understood the first parameter is the one it gets from the left. And perhaps that could be blank:

>> parameters of :add
== [_ value1 value2]

>> parameters of :+
== [value1 value2]

Plus, it seems like skippability is important enough it may warrant being a separate parameter class (maybe the @param notation?) There's no way to make skippable parameters that aren't hard quoted, so that's implied.

We can go with having enfixedness it be a "hidden" flag...but if it's hidden it's easy to forget to check for it. And I feel like making it a separate datatype pushes enfixedness into a category of "otherness" that's no longer a fit...it's a spectrum of parameter conventions for when a function is invoked without APPLY or an explicit FRAME!.

Regardless, I think it has to be an action property...

It has been interesting to experiment with infixedness being a WORD! property. The enfix flag pioneered cell properties that weren't transferred when cells moved--which now is used for tons of other things.

But I think at the end of the day, the advantages are outweighed by making it an intrinsic property of the action itself...that travels with the action when you reassign it. The relationship between ADD and + is truly the outlier class. Most modern routines are designed to function with a particular interface and expectation of enfixedness or not.

Significant improvements to APPLY are going to pick up the slack on receiving functions from unknown sources. So I think things are going to work out for the better.