Here's an interesting cookbook recipe. Frequently, the goal of a COLLECT process is to collect a certain number of strings representing command lines, or something like that. Each line is represented by a block, but needs to be SPACED. This can be a bit annoying to have to say every time:
collect [
keep spaced [...]
if condition [
keep spaced [...]
]
]
What if you wanted to specialize COLLECT as COLLECT-LINES so it would do the SPACED automatically?
collect-lines: adapt 'collect [
body: compose [
keep: adapt 'keep [value: spaced value]
(as group! body)
]
]
So you're augmenting the body with a little bit of prelude code that adapts the keep. What's nice is that by using AS you don't need to deep copy that body, you're just aliasing it. This means that when COLLECT goes through and binds the augmented body to keep, the little adapter has the same binding...so it affects that keep.
There's a particular finesse in Ren-C because when you put something in a stream of code using AS GROUP!, that won't synthesize anything. If your group is empty, it will act like it's not there.
>> do [1 + 2 ()] == 3
That's actually pretty important for when you're doing these kinds of code splicings, you can really "opt out" of sections.
If you're going to do it this simply, you can eliminate some of the repetition with MY (which quotes a set word on the left, and injects the value as the first parameter of what comes next).
collect-lines: adapt 'collect [
body: compose [
keep: my adapt [value: my spaced]
(as group! body)
]
]
That's quick and dirty enough for casual usage. But this looks nice enough we might even want it in the box.
So thinking through a few edge cases...
Some of the refinements to KEEP don't make sense any more, like /ONLY and perhaps /PART (you'd be passing in a block and then specifying a /PART based on the text...probably not what you meant). You probably want each line to be on its own line when collected in the list, so /LINE should be true. Other refinements, like /DUP can probably be left as-is.
So let's get rid of the refinements that don't make sense and set it up to default to a newline on each string in the collected block. Also, you need to TRY on the value because it might be null, and then you'd want SPACED to see it as a BLANK! instead of an error, returning a null and thus preserving it:
collect-lines: adapt 'collect [
body: compose [
keep: adapt specialize 'keep [
line: true | only: false | part: _
] [value: spaced try :value]
(as group! body)
]
]
Now we have a nice little routine:
>> collect-lines [
keep ["How" "about" "this?"]
keep case [
1 = 2 [["Not" "Kept"]]
3 = 4 [["This" "Neither"]]
]
keep/dup ["Pretty" "cool" "eh?"] 2
]
== [
"How about this?"
"Pretty cool eh?"
"Pretty cool eh?"
]
So... how difficult would that be to do in Rebol2/R3-Alpha/Red? And how likely are you to get it wrong while trying?
One issue to think about...
With the recent change to where COLLECT only creates a block if you do a KEEP of some non-null material, there was a workaround to say keep []
as a no-op at the top of the collect body, to get the block. That won't work here, since keep []
will add an empty string to the collected lines.
Off the top of my head:
-
Since COLLECT-LINES is not "full band" any more (it knows you don't want to collect a BLANK!), it might use BLANK! to be the "no-op, but means you kept something". So
keep _
would execute the un-adaptedkeep []
internally, yielding the same effect. -
COLLECT-LINES could just sneak in a
keep []
before it does the specialization and always return a block, foregoing COLLECT's "null if no KEEPs" property.
What's nice about actually doing these little experiments is you get to think about what pressures it puts on routines like SPACED. Increasingly I am of the opinion that SPACED of a TEXT! should just return that text--there's almost no case where enforcing that it's a BLOCK! has value.