Good news: an old issue is (seemingly) mostly addressed!
Among the various implications of this design improvement, you can AUGMENT a function with new fields that share the name of either locals or specialized values. The only names you cannot use in extending a function are those that are public parameters on the interface!
>> ap10: specialize :append [value: 10]
>> ap10 [a b c]
== [a b c 10]
>> wow!: adapt (augment :ap10 [/value [integer!]]) [insert series value]
>> wow!/value [a b c] 20
== [20 a b c 10]
So what's going on here is that underneath the hood, the single FRAME! for this function call has two slots with the label value
. But they're never in effect and visible at the same time. This is great news for composability of functions.
I'm going to try to explain here a little bit of how this works.
Every Function Is Defined By an "Exemplar" FRAME!
Some time ago I penned the prophetic post: "Seeing all ACTION!s as Variadic FRAME! Makers". This set the stage for what ultimately became an implementation mechanism where the interface to all ACTION!s are defined by a FRAME!.
So if you write something like:
foo: func [return: [] x [tag! text!] y [integer!] <local> z] [
print ["internal foo view:" mold binding of 'x]
]
Inside of FOO there is a FRAME! that lays out a map of the parameters and locals. This is called the "exemplar". We can get direct access to that frame:
>> exemplar of :foo
== make frame! [
return: make typeset! []
x: make typeset! [#[datatype! text!] #[datatype! tag!]]
y: make typeset! [#[datatype! integer!]]
z: ~
]
This isn't an "ordinary" frame for the function. The fields don't hold legitimate values for a function invocation...they are holding typesets. Except for Z which is a local, so it holds the value that it will have when a frame is made. (more on that in a second)
So now let's try making an ordinary frame for the function:
>> f: make frame! :foo
== make frame! [
x: ~
y: ~
]
Okay, that's neat. It doesn't seem to have the RETURN or Z fields because we aren't supposed to be setting those. They are there--the memory is part of the frame, and part of what will actually be backing the variables when you DO the function. But they are hidden in this "phaseless" view.
I put code inside the function to print out its internal view of that same frame. Let's try running and see what it says:
>> f.x: "Hello"
>> f.y: 1020
>> do f
internal foo view: make frame! [
return: '#[action! [^value /isotope]]
x: "Hello"
y: 1020
z: ~
]
Hey, look at that. When we see the frame from inside the function, it has access to RETURN and Z. How does it know to hide the fields on the outside, but give access to them on the inside?
The answer is that each FRAME! value can optionally hold a "phase". A phase is itself just an ACTION!--it's which step of the composition you are running. The phase informs which of the fields are supposed to be visible.
Now, Let's SPECIALIZE It...
Let's make a new function BAR which fixes the value of Y.
spfoo: specialize :foo [y: 304]
And now let's look at what its internal "fake" exemplar FRAME! looks like:
>> exemplar of :spfoo
== make frame! [
return: make typeset! []
x: make typeset! [#[datatype! text!] #[datatype! tag!]]
y: 304
z: ~
]
Something you'll notice is that the type information for Y is now lost, and the slot where the type information would have been has been replaced by the specialized value. That's a nice little efficiency trick.
Now if we make a frame for SPFOO, the only thing it will let us set is X:
>> f: make frame! :spfoo
== make frame! [
x: ~
]
What if We Were to ADAPT the Specialization?
So this raises an interesting question about the "inside" and "outside" view of things.
At an interface level, I would argue that it should not usually be possible to tell the difference between SPFOO and any other function that takes a single parameter X.
So what happens if we ADAPT the SPFOO function and get access to the frame on the inside?
adspfoo: adapt :spfoo [
print ["inside adaptation:" mold binding of 'x]
]
>> adspfoo "What happens?"
inside adaptation: make frame! [
x: "What happens?"
]
internal foo view: make frame! [
return: '#[action! [^value /isotope]]
x: "What happens?"
y: 304
z: ~
]
Ta-da. ADAPT only saw a function with an X parameter, and none of the other details are exposed to it. Its view of the frame only sees X. But it's all the same frame... memory is being reused, just the access to it is controlled.
Pretty slick, huh? Anyway, I'm sure there are bugs but the groundwork is there. Please experiment and let me know if anything seems to be counterintuitive.
(There's a lot of thinking that needs to be done about how RETURN plays into this...it's a weird case that needs study. Being inside the ADAPT and not having access to the RETURN is irritating, but it may just be necessary to prevent that access and make you use an ENCLOSE if you might want to return.)