AUGMENT is a new addition to the function composition toolbox. It serves a single purpose: to create a variation of a function that has more parameters and refinements, but acts exactly the same.
>> foo-x: func [x [integer!]] [
print ["x is" x]
]
>> foo-xy: augment :foo-x [y [integer!]]
>> foo-x 10
x is 10
>> foo-xy 10
** Error: foo-xy is missing its y argument
>> foo-xy 10 20
x is 10
You might ask: "What good is that, since the original function has no idea the parameter is there?" This is where our friends like ADAPT and ENCLOSE come in.
Let's try that again. First, with an ADAPT:
>> foo-xy: adapt (augment :foo-x [y [integer!]]) [
print ["y is" y]
]
>> foo-xy 10 20
y is 20
x is 10
And here's an ENCLOSE example:
>> foo-xy2: enclose (augment :foo-x [y [integer!]]) func [f [frame!]] [
let y: f.y
print ["y is" y]
do f
print ["y is still" y]
]
>> foo-xy2 10 20
y is 20
x is 10
y is still 20
This didn't drop out of the sky...
It is the result of long-term-thinking, and design choices with an eye toward doing this someday. One of the recent strategic moves that really made it feasible was changing refinements to be their own arguments. If it weren't for that, if your function was foo: func [x /y] [...] and you tried bar: augment 'foo [z], the parameter list would look like you had written bar: func [x /y z] [...]. As soon as a function got a refinement, you'd have no way to add a normal parameter...because everything would become an argument to the last refinement.
The very first motivating scenario which got me thinking about this was when I was lobbying for removing the /DEFAULT refinement from SWITCH. I felt strongly about needing a generalized solution based on NULL results from branching constructs. But I wanted it to be easy to make a compatibility version.
And now, it is easy:
switch-d: enclose (augment :switch [
/default "Default case if no others are found"
[block!]
]) func [f [frame!]] [
let def: f.default ; see NOTE on why it's not `do f else (f/default)`
do f else (def)
]
It works the way you'd expect:
>> switch-d 1 [1 [print "one" 1020]]
one
== 1020
>> switch-d/default 1 [1 [print "one" 1020]] [print "defaulting!" 304]
one
== 1020
>> switch-d/default 2 [1 [print "one" 1020]] [print "defaulting!" 304]
defaulting!
304
I'm not totally thrilled with the way the meta information for HELP is being inherited. But the somewhat hackish way it is done is working well enough to get us started. Note how the description for /DEFAULT was incorporated:
>> help switch-d
USAGE:
SWITCH-D value :predicate cases /all /default
DESCRIPTION:
Selects a choice and evaluates the block that follows it.
SWITCH-D is an ACTION!
RETURNS: [<opt> any-value!]
Last case evaluation, or null if no cases matched
ARGUMENTS:
value [<opt> any-value!]
Target value
:predicate [refinement! action! <skip>]
Binary switch-processing action (default is /EQUAL?)
cases [block!]
Block of cases (comparison lists followed by block branches)
REFINEMENTS:
/all
Evaluate all matches (not just first one)
/default [block!]
Default case if no others are found
This is a very new mechanism that is going to need testing. But it's going to make many things easier--not just in implementing things like Redbol, but also in being able to create skins with warnings about deprecated refinements (and what to do instead)...while removing those refinements from the natives themselves.
Try it out!
NOTE: Although I'm now of the belief that function arguments must outlive their calls, I don't believe this implies that do f should not invalidate the caller's handle on that f frame. It is an effective transfer of ownership of that frame to the function; and you need the feedback that you cannot expect another DO to work again. Hence the default must be cached.