Taking ACTION! on "Function vs. Action"

Something sort of clicked around in my head when I wondered...

What if ACTION!s were just antiform FRAME!s?

:exploding_head:

It looks... good.

foo: function [frame [frame!]] [if true [run frame arg1 arg2]]

bar: function [action [action?]] [if true [action arg1 arg2]]

baz: function [frame [frame!]] [let action: runs frame, action arg1 arg2]
  • It's clear that FRAME won't do anything when you reference it from a word

  • It's clear that ACTION will run when you reference it from a word

  • It leaves the word FUNCTION free (which over the ages has been determined non-negotiable as semantically meaning MAKE-FUNCTION)

  • Mutating between a frame and an action is easy and obvious

Can It Work?

ACTIONs and FRAME!s actually line up very closely. They each have as many slots as a function has parameters and locals. But they use these slots differently.

ACTIONs hold type checking information in the slots for arguments, and the frames point to the actions they were made from to get this information. This is because the FRAME! slots need to hold actual argument values. When you DO a FRAME!, it follows the link it kept to its action to do the type checking.

Yet in some sense, this is an implementation detail. There's not really a reason why every frame couldn't have made its own copy of the parameter descriptions...and no reason why the original frame couldn't have empty argument slots. You could just say that immutable frames "throw away" their argument slots...and copied frames inherit their parameter lists.

The differences could be glossed over by magic under the hood, retaining the current efficiency. Basically the FRAME! datatype cell would just be able to hold the guts of what an ACTION! cell currently holds as a potential alternative to what it holds currently, and expose those guts as if it were an immutable frame.

Usage Difference

As currently designed, the system would have a bit of a chicken-and-egg problem... you need an ACTION! to make a FRAME!... e.g. make frame! :append

But if APPEND was an antiform frame, and the MAKE FRAME! in this case was just doing basically copy unrun :append ?

In such a world, you'd presumably want to PROTECT or CONST the frame behind APPEND, or you'd wind up with it being too easy to change functions:

>> f: unrun :append
>> f.value: [d e]  ; is actually specializing APPEND!

>> block: [a b c]

>> append block [x y z]
== [x y z]

>> block
== [a b c [d e]]

But that's fixable with a trustworthy system of immutability (which Ren-C has).

A plain FRAME! that was found inline would have to execute a copy of itself, the way a plain action does today, so this wouldn't do what you likely expected:

make object! compose [f: (frame)]  ; would execute the frame

This isn't exactly a dealbreaker, because it's par for the course:

make object! compose [w: (word)]  ; would evaluate the word

So you'd have the usual tools available, same as for other types:

make object! compose [f: '(frame)]
make object! compose [f: (quote frame)]

make object! compose [f: ^(frame)]
make object! compose [f: (meta frame)]

Trying This Would Raise Many Questions

It's a bizarre thing to do if the only reason is to sift out the naming for a word-inactive vs. word-active function invocation. Though it does seem to solve it nicely: "ACTION!s are antiform FRAME!s which DO (a COPY of) themselves if referenced via WORD!"

But there are other advantages to reducing the total number of exposed types.

I can think of one advantage already...which is the PARSERs passed to combinators in UPARSE. Today they are ACTION!s formed from specialized frames, to make them easy to call. But I noticed that passing them as FRAME!s would be cheaper...as well as permit the choice to execute the frame directly without making a copy, if you were going to call it only once.

There's a disadvantage I can think of too... which is loss of flexibility for interpretation of field selection out of ACTION!s. Under this model, append.value should be an unset value...as APPEND is an immutable "archetype" frame. I'd hoped that the action.xxx space could be taken for special fields that were associated with the functions, things like append.help for example would be picking from an object associated with APPEND, as opposed to picking useless fields out of the unspecialized frame that APPEND represents.

(Of course, Ren-C has other tricks up its sleeve, and might be able to do this with something like append..help for instance. Today this information is accessed through the peculiarly-named adjunct of)

Anyway, still just a thought. But maybe a unifying thought that's good enough to see what came up trying to implement it.

1 Like