(No) Alternative Local Notation in the Func Spec Dialect

TL;DR:

When using a spec in FUNCTION (remember FUNC is a synonym now):

  • if you want to specify locals you say [... <local> x y z ...] (/LOCAL is a normal refinement like any other in Ren-C.

  • there is no longer a datatype-based shortcut for locals as a way to save the trouble of finding <local> in specs and inserting at the right point.

    • Once you could toss random SET-WORD!s anywhere in a spec, like [a b c: d] and it would act like [a b d <local> c]. This feature was to make higher-level function generators easier to write, to save them from having to find <local> in the spec...add it if not there...and do everything at the right point.

    • When multi-returns began using SET-WORD! this was changed to be [a b .c d] just to move it out of the way.

    • What this was trying to achieve has been obsoleted by newer and better methods, which actually entwine with the idea that you should never know what local variables a function you are composing on top of has.

More Explanation

Long ago, when I was looking at some of the higher-level function generators (like FUNCT on top of FUNCTION) I was struck by how tricky it was to get the adaptations written correctly.

We can look at FUNCT from R3-Alpha and see some of that complexity, even just to try and add some local variables.

It had to check to see if there was already a /LOCAL, and add it if not:

; Copy the spec and add /local to the end if not found
unless find spec: copy/deep spec /local [append spec [
	/local ; In a block so the generated source gets the newlines
]]

Bear in mind that specifying /LOCAL twice would be a duplicate refinement error, and /LOCAL did not have to be at the end of a spec. Things were tricky, because there were "private refinements". These refinements were an artifact of how help worked--they were not shown--but weren't actually private.

So you had to be careful, to insert things after local but not after the private refinements, or they'd be arguments to those refinements:

; Collect all set-words in the body as words to be used as locals, and add
; them to the spec. Don't include the words already in the spec or object.
insert find/tail spec /local collect-words/deep/set/ignore body either with [
   ...
]

I wanted to take better advantage of datatypes, so I adopted the TAG! <local> in the spec so that /LOCAL could be used like any other refinement... e.g. GET-TIME/LOCAL or GET-LANGUAGE/LOCAL. This is an idea preserved to this day, and I think it's a better move (though LET has been developing as the more viable general option for making locals).

But another concept I had was to make SET-WORD! in the dialect be another way to put a local anywhere in the spec. So if you were writing code munging function specs you could just throw local variables on:

foo: func [a b c: d] [...]

<= equivalent to =>

foo: func [a b d <local> c] [...]

But the idea was that <local> itself was transformed into the SET-WORD!, so the only language you needed to understand was that of the SET-WORD!s:

>> foo: func [a b d <local> c] [...]

>> spec-of :foo
== [a b d c:]

When multi-returns came about and took SET-WORD!s, this idea was bumped around to be done with TUPLE!s with leading blanks like .c

... but Everything Changed :robot: ...

I've been marching toward something more like "Seeing all ACTION!s as Variadic Frame Makers?"

Function composition tools operate on FRAME!s using tools like ADAPT, SPECIALIZE, AUGMENT, ENCLOSE, etc. There is no API for getting at the locals because when you build on top of a function you can't see them, they're not part of the interface...they are sealed inside. This is good because you can build on top of a function without worrying about the details of the parts from which it was composed.

The migration to where refinements are their own arguments has been key in making function calls easier to model as an object.

In this world we do not need a representation for locals in the spec to add them...because the entire concept of how FUNCT was written is gone. If you want to add locals you use tools like AUGMENT and don't worry about it.

There Are Still (many) Problems To Solve

Consider that you can do something like ask for a FRAME! of something like APPEND, then enumerate the keys:

>> f: make frame! :append

>> for-each key f [print mold key]
return
series
value
part
dup
line
only

There are a lot of questions to answer about the unique nature of the main function's RETURN. Although other multi-returns have to be on the interface, should you be able to put a variable in that return? Consider that you could use multi-return with append today:

>> [var]: append "abc" "d"
== "abcd"

>> var
== "abcd"

So should you be able to preload the frame with f.return: 'var and get the same effect? This is competitive with the notion that RETURN is a concept of a local variable to the action, that holds an action that returns specifically from that action... so the responsibilities are somewhat murky for this distinguished result.

But we can also see that if we only look at object keys, we don't know which things are refinements or which are not...if they are refinements we don't know if they are 0-arg or 1-arg form, we don't know what types they take... or are they <end>-able. I believe getting at this information should be done by means other than analysis of some spec block you filter out for description strings/etc. so that is on the right track. But it's still a long road.

2 Likes