COLLECT allows you to build up a block, without needing to name the block or pass it as a parameter to individual APPEND instructions. Instead you use KEEP, which appends to the implicit nameless block:
>> collect [
keep 'foo:
print "Arbitrary code possible"
keep/line [1 2 3]
keep spread [Spread #works @(T O O)]
repeat 2 [keep <whatever>]
]
Arbitrary code possible
== [
foo: [1 2 3]
Spread #works @(T O O) <whatever> <whatever>]
Leverages LAMBDA To Bind KEEP To Code
The trick is that the body is turned into a function that takes KEEP as a parameter. This defines the word KEEP for the body.
To see how this works, imagine this:
collector: lambda [keep [action?]] [
keep 'foo:
print "Arbitrary code possible"
keep/line [1 2 3]
keep spread [Spread #works @(T O O)]
repeat 2 [keep <whatever>]
]
block: copy []
keeper: specialize :append [series: block]
collector :keeper
This code gets the desired result in BLOCK.
Slight Twist: Make KEEP Return Its Input
APPEND will return the block that you append to. This would reveal the partially-built temporary block before the collect is complete. A better and more useful result of KEEP would be to return the value that you pass it.
To accomplish that, we can ENCLOSE the specialization:
keeper: enclose (specialize :append [series: block]) func [f [frame!]] [
let value: f.value
do f
return value
]
We have to capture the value to append before we DO the captured FRAME!, because Rebol functions are permitted to make arbitrary modifications to their arguments during execution. (To help avoid mistakes, you are not allowed to read a frame's values after a DO is complete.) It's possible to DO COPY F but that makes a copy of the entire frame, and here we just copy the value we want.
A more efficient way to do this is to use a LAMBDA for the wrapper function, and ELIDE the DO. There's no need to type check F (since ENCLOSE only passes the FRAME! built for APPEND, never anything else):
keeper: enclose (specialize :append [series: block]) lambda [f] [
f.value
elide do f ; evaluates to anti-isotope of 0 length block, vanishes
]
Putting It Together
Wrapping this up for a working COLLECT implementation:
collect: func [
return: [block!]
body [block!]
][
let block: copy []
let keeper: enclose (specialize :append [series: block]) lambda [f] [
f.value
elide do f
]
run (lambda [keep] body) :keeper
return block
]
It's a good demonstration of how you can make something impressive that feels like a first-class language feature out of Rebol, with little effort.