Introducing REFRAMER: Close Cousin to ENCLOSE

This is a very cool tool that might help concretize some of the abstract-sounding arguments I was making about frames...

Reframers build a frame from whatever follows them at a callsite, and can operate on it before running it (if it runs it).

Defining a reframer involves giving it a function that will act as the "shim". Here is a reframer using a very simple shim, that just takes in the frame and returns it.

get-frame-of: reframer (func [f [frame!]] [f])

Here is what happens if the thing that followed that reframer's execution was a call to APPEND:

>> get-frame-of append [a b c] <d>
== make frame! [
    series: [a b c]
    value: <d>
    part: '
    only: '
    dup: '
    line: '
]

As we can see, it gathered the arguments for the APPEND and put together a FRAME! to represent the call.

The reframing process did not automatically execute the frame. But the "shim" function can!

Let's try something that runs the function twice.

>> two-times: reframer func [f [frame!]] [do copy f, do f]

>> two-times append [a b c] <d>
== [a b c <d> <d>]

Whoa.

But wait, there's more. Besides not automatically executing the FRAME!, it also doesn't typecheck it (yet).

>> get-frame-of append 1 <d>
== make frame! [
    series: 1        ; !!! this wouldn't be legal to run as-is 
    value: <d>
    part: '
    only: '
    dup: '
    line: '
]

With our "shim" function in the driver's seat, it can manipulate the inputs and the results.

Consider how right now, functions like APPEND won't take QUOTED!

>> item: first ['''[a b c]]
== '''[a b c]

>> append item <d>
** Script Error: append does not allow QUOTED! for its series argument

But what if we made a REQUOTE that:

  • would build a frame for whatever follows it
  • counted how many quoting levels were on the first argument in that frame
  • took the quoting levels off that first argument
  • ran the function
  • added the quoting levels back to the result

It should be an easier function to write than it is, but even so it's not that hard:

requote: reframer func [
     {Remove Quoting Levels From First Argument and Re-Apply to Result}
     f [frame!]
     <local> p num-quotes result
][
    p: first words of f
    num-quotes: quotes of f/(p)
    f/(p): dequote f/(p)

    if null? result: do f [return null]  ; exempt NULL from requoting

    return quote/depth get/any 'result num-quotes
]

And behold:

>> item: first ['''[a b c]]
== '''[a b c]

>> requote append item <d>
== '''[a b c <d>]

The Shim Can Take Arguments

The last argument of the shim needs to be a frame, but it could also have its own arguments.

Just to demonstrate this point without any frame-fiddling to obscure the point, how about a message that prints before and after just executing the frame:

>> bracketer: reframer func [msg [text!] f [frame!]] [
       print msg
       do f
       print msg
   ]

>> bracketer "Aloha!" print "I'm being framed!"
Aloha!
I'm being framed!
Aloha!
== ~void~

Things To Think About

This is very cool, and it pins down a number of questions about evaluation.

Hopefully now you can see why type errors shouldn't happen during argument fulfillment, but only once the function actually gets to the point of running. e.g. a reframer that just does the function after it shouldn't act any different than that function would running normally.

One tough problem is what to do when you get multiple reframer functions in a row. I give the example of:

>> item: first ['''[a b c]]
== '''[a b c]

>> item: my requote append <d>
 ; ... how can this work?

MY is also a reframer. But if it gets a FRAME! for REQUOTE, that will not be what it expects. Because REQUOTE has a single argument in its frame...which is a frame, not a callsite argument. :-/

What MY really wants is a FRAME! for the aggregate function of "REQUOTE APPEND". Such aggregate frames aren't impossible to conceive of, but are beyond what we have today.

That's probably the biggest issue I can see right now with this. But it's a step ahead of having to reinvent the technique on every function that wants to do something like it. And it means that when an answer for one such functions is made, all of them will get it.

2 Likes