Function Spec Dialect: Who Should Analyze It, And How?

This is coming closer and closer to reality.

You've been able to go both ways for a while now: either MAKE FRAME! from an ACTION!. or MAKE ACTION! from a FRAME!. But I've managed to do some changes that make this process more fluid and less expensive.

How much less expensive? MAKE ACTION! from any FRAME! now creates an entity that takes up just 8 platform pointers. Before, creating actions always required creating a new parameter list to serve as the action's identity...so how much it cost would depend on how many arguments and locals you had. You'd need the same 8 platform pointers...plus 8 more platform pointers (archetype and end), plus 4 platform pointers per argument or local...plus round up for fitting in a memory pool.

This does make it very appealing to start thinking of making functions in a fast and direct way that doesn't necessarily go through any "spec parsing"... and the spec language would no longer be built into the interpreter or evaluator.

Lots of Open Questions, Though.

Right now, you have a chicken-and-egg problem when it comes to making ACTION!s out of FRAME!s... because the only way to get a FRAME! is from an existing ACTION! (which has encoded knowledge of what's a refinement, argument, local, quoted argument, etc.)

What will it take to be able to MAKE FRAME! from scratch, with information beyond just the key names? And how do you give the frame behavior?

Brainstorming here: Perhaps it's a two step process, where making a frame from a BLOCK! sets up the parameterization with no initial behavior outside of type-checking. Then you adapt it:

 frame: make frame! [
     return: <return>
     arg1: integer!, arg2: integer!
     hide local1: ~undefined~
 ]
 add-nums: adapt (make action! frame) [
     local1: arg1 + arg2
     return local1
 ]

That's a rough idea with a lot of hand-waving, but maybe it's pointing in a useful direction. And even a weird notion that the non-hidden contents of frame values are their parameter gathering disposition...so if you ever wanted to specialize something, you'd actually hide it.

>> f: make frame! :append
== make frame! [
    series: [any-series! port! map! object! module! bitset!]
    value: [<opt> any-value!]
    part: [any-number! any-series! pair!]
    only: []
    dup: [any-number! pair!]
    line: []
]

>> hide f/dup: 2  ; hiding means frame content is value, no longer typecheck
>> f/value: [integer!]  ; not hiding, so adjusting typecheck
>> ap2int: make action! f

>> ap2int [a b c] 10
== [a b c 10 10]

>> ap2int [a b c] "illegal"
** Error: ap2int doesn't allow TEXT! for its VALUE argument

Anyway, just trying to push a little of this thinking forward as to more what it might look like. One way or another, I see FRAME! moving to a role of structural primacy in the evaluator...with spec blocks and their variations being designed to match the needs of their uses.

Some More Details on New Efficiencies

Without belaboring too many details of recent housecleaning...I've managed to align the workings of contexts and actions a bit better:

  • Since the beginning of FRAME!, parameter lists of actions have been able to serve as the "keylist" of contexts. But they were also acting as the identities of functions. This meant every time you would ADAPT or SPECIALIZE something with 13 arguments and locals...you'd have to copy that 13-long array to get a new identity.

  • In the beginning, a frame was dependent on being able to glean an action's specific identity from the paramlist...because that's the only pointer it had. As the design of "phase" information in the FRAME! cell got further along, this was no longer needed; the function identity could be known every step of the composition just by the phase.

  • Other changes consolidated that the paramlist no longer needed to be the identity of an action, and could thus be shared between them...with another array holding instance information about the action (such as the body block to run) being the identity.

Not quite finished, but largely a clarifying change that reduces memory footprint. Some performance work will be needed since it has to do calculations to find things that used to be closer at hand, but I don't think it should be too bad. Biggest impact is that some of the parameter hiding was based on having new paramlists at each level, so it will have to be seen if frame phasing can achieve the same effect...or if it's an acceptable loss to not hide access to locals in some cases (like ADAPT)

1 Like