Big Alien Proposal 🛸 "/WORD" Runs Functions

I managed to get a booting system where you have to use /foo: assignments to get functions to execute when dispatched by WORD!.

TL;DR - I Don't Think I Like The First Attempt... BUT...

It's valuable exploration, to look at the options and think about alternative designs.

I Explored Having FUNC Not Return FRAME! Antiforms

Having Action be "FRAME!s at quote level -1"... that cannot be put in blocks, and can represent a non-literal sort of usage of actions... is very new. Like a-couple-weeks-ago new.

And in the first cut at implementing them, consider what would need to be true for this to work as expected:

foo: func [x] [print [x]]

In this world, for the word FOO to run the function, that meant the return product of FUNC would have to be a function antiform.

That got ugly. The whole idea of antiforms is that most routines don't touch them, and error when they encounter them. But this meant every generator (FUNC, LAMBDA, ->, ADAPT, SPECIALIZE, CHAIN, ENCLOSE...) had to make antiforms. And everything that processed or composed functions in any way had to either take antiforms or force you to ^META (or "UNRUN") your generated functions.

I tried throwing in random decaying from antiforms to normal actions, but it felt uncomfortable.

That's because I imagined no one would want to write the "clean" answer:

foo: anti func [x] [print [x]]

And probably wouldn't be much better with a specialized native like RUNS that only antiformed actions (in the spirit of how SPREAD only antiforms lists):

foo: runs func [x] [print [x]]

I suggested even stealing something light like arrow:

foo: -> func [x] [print [x]]

But the search for minimal also dovetailed with the desire for lib/append to reasonably imply /append was a function. So that's part of what birthed the comparatively-palatable idea of slipping it into the assignment with a single easy-to-type character:

/foo: func [x] [print [x]]

So it's good to try that out. Not having weird decay operations everywhere feels more stable.

But...

Incongruity With Refinements Feels Worse Than I Thought

We've already discussed the overlap with APPLY, where leading slashes don't mean function execution, like:

 apply :append [series value /dup 3]

But here, take a look at trying to move the CASE refinement name out of the way so that CASE the function can be used inside a function:

/alter: func [
    return: [logic!]
    series [any-series! port! bitset!] {(modified)}
    value
    /case "Case-sensitive comparison"
][
    case_ALTER: case
    /case: :lib.case
    ...
]

I'm not bothered by the number of slashes. I'm bothered by them meaning such unrelated things, yet being used so close to each other in such a core construct.

Other Than That, Slash Kinda Helped Code Readability

If you ignore the refinement debacle, there are some perks. If we tie together slashiness with "runs a function", then I've already pointed out how this feels pretty natural:

obj: make object! [
    data: 1
    /accessor: does [return data + 1]
]

But additionally, there are lots of "function generators" out there, with even more in the future. If you see something that says:

something: make-processor alpha beta gamma

Knowing if that SOMETHING is an action or not is a fairly important thing. It means you have to think about how you handle it carefully, even if just passing it around to somewhere else. So if you see:

/something: make-processor alpha beta gamma

You have to know the function-or-not at the callsite. Given that you have to know it when you reference it, then it seems to make some sense to declare it.

But I feel uneasy about treading on refinement's turf. And among things in Rebol that seem actually kind of decent, the refinements in function specs actually do feel kind of "good".

Forgetting The Slash Was Certainly Annoying

We don't have any new-users who are only habituated to a world where you have to put in the slash. So there's just going to be people who are used to not needing it, forgetting it.

This does bring about a shift from the "over-execution" of functions when you forget a GET-WORD!, to an "under-execution" of functions when you forget a slash.

Any Synthesis Of Ideas Here That's Not Terrible?

It should be remembered that antiform-based Actions are new, and their potential benefits are not completely understood yet.

We've seen great benefit from the antiform blocks, resolving the /ONLY debacle. And there are ideas behind antiform actions and typesets and such, for solving similar problems of wanting non-literal-value semantics.

The part I liked about this prototype was making things like FUNC and SPECIALIZE and ADAPT and everything else just return regular FRAME!. It felt cleaner to shift this burden of things becoming antiforms onto the caller, because otherwise the burden was on decaying antiform actions to regular actions. That burden showed up when fulfilling action parameters, or inside COMPOSE or REDUCE, etc.

But looking at the two strategies side-by-side, I have to say that it's probably better to lean into the "generator functions" producing antiforms, and work from there.

3 Likes