# Interface and implementation of this replacement for Rebol/Red REJOIN

This is a draft implementation of a function called COMBINE. It's not intended to be a complex formatting dialect, just a replacement for REJOIN that has a better name and more useful behavior. Its purpose is laid out pretty thoroughly in this blog entry:

http://blog.hostilefork.com/combine-alternative-rebol-red-rejoin/

But to make a long story short, it is supposed to let you flatten expressions into a string. If an expression returns a block, then that block will be recursively evaluated using the same rules as if it had been handed to COMBINE directly:

combine: function [
{Combine evaluated expressions and substitute block variables to produce a string}
block [block!] "Specification to process"
/with "Add delimiter between values (will be combined if a block)"
delimiter [block! any-string! char!]
/into "Insert output into an existing string instead of making a new one"
out [any-string!]
/local
value
] [
;-- No good heuristic for string size yet
unless into [
out: make string! 10
]

if block? delimiter [
delimiter: combine delimiter
]

needs-delimiter: false
pre-delimit: does [
either needs-delimiter [
out: append out delimiter
] [
needs-delimiter: true? with
]
]

;-- Do evaluation of the block until a non-none evaluation result
;-- is found... or the end of the input is reached.
while [not tail? block] [
set/any 'value do/next block 'block

;-- Blocks are substituted in evaluation, like the recursive nature
;-- of parse rules.

case [
unset? :value [
;-- Ignore unset? (precedent: any, all, compose)
]

none? :value [
;-- Skip all nones
]

any [
function? :value
closure? :value
] [
throw make error! "Evaluation in COMBINE gave function/closure"
]

block? value [
pre-delimit
out: combine/into value out
]

any-block? value [
;-- all other block types as *results* of evaluations throw
;-- errors for the moment.  (It's legal to use PAREN! in the
;-- COMBINE, but a function invocation that returns a PAREN!
;-- will not recursively iterate the way BLOCK! does)
throw make error! "Evaluation in COMBINE gave non-block! block"
]

any-word? value [
;-- currently we throw errors on words if that's what an
;-- evaluation produces.  Theoretically these could be
;-- given behaviors in the dialect, but the potential for
;-- bugs probably outweighs the value (of converting implicitly
;-- to a string or trying to run an evaluation of a non-block)
throw make error! "Evaluation in COMBINE gave symbolic word"
]

true [
pre-delimit
out: append out (form :value)
]
]
]
either into [out] [head out]
]


Pretty much anything here is up for review; both the interface and the implementation. A few things to wonder about:

• What would be a good heuristic for the output size?

• Right now combine [if (1 > 2) {something}] returns an empty string. Initially I thought that if it only received NONE, then it should return NONE. But the existence of the /INTO refinement makes that not possible to distinguish, as it's supposed to return the insertion position. Is this distinction more important than offering /INTO to the caller (rejoin didn't...).

• How should other string types be returned? Currently the only way to get a non-string is to use /into, in order to avoid the sensitivity of the type of the first element that REJOIN has. But should there be an /AS refinement for passing the target type?

• out: append out delimiter => append out delimiter – Graham Chiu Jun 20 '14 at 0:24

Some thoughts:

## Initialize String

unless into [
out: make string! 10
]


Don't know how else to discern the initial string allocation, perhaps just start with zero?

OUT might be a strange word for a value that is both input and output. I'd also allow a NONE! value here.

/input out [any-string! none!]


Then you can initialize like:

out: any [out make string! 0]


## Delimiter

if block? delimiter [
delimiter: combine delimiter
]


Cool, a delimiter can be combined on the fly!

needs-delimiter: false
pre-delimit: does [
either needs-delimiter [
out: append out delimiter
] [
needs-delimiter: true? with
]
]


Rather than an extra function and an extra value for this, why not just an extra value:

active-delimiter: none


Then set active-delimiter to delimiter after the first step through BLOCK.

## Iteration

while [not tail? block] [
set/any 'value do/next block 'block
...
]


I'm not certain about this, but I believe FOREACH is faster than WHILE. This seems like an inefficient way of expressing:

foreach value reduce block [
...
]


## Functions

function? :value
closure? :value


Are you just testing for these two types of function? Otherwise ANY-FUNCTION? would catch both.

## Throwing Errors

throw make error! "Evaluation in COMBINE gave function/closure"


I don't think this works in Rebol 3, at least not without wrapping the function call in a CATCH block. As I understand it, you need to DO an error to trigger it.

## Coerce Output Type

An /AS refinement might be a good way to do this, so long as the type is with the ANY-STRING! typeset. It'd be a close call as to whether /INTO or /AS would take precedence for output type:

type: any [type string!]
out: any [out make type 0]


## None/Empty

My opinion here is:

combine []
combine [none]


Should return ""

combine/with [none none] ","


Should return ","

• I'll incorporate this. At first I was reluctant about the combine/with [none none] "," producing ",". However I think that if you have a series of evaluations that may give none and you want to bump them into a comma group the solution is effectively combine/with [[none none]] "," to get "". So going with that. As for the error, I don't know what the right way to do it is... but loop 10 [throw make error! "woo"] works, it just doesn't give a very informative message. – HostileFork Jun 24 '14 at 19:23
• do make error! "..." is the right way to cause a user error such as this. Alternatively, you can also cause one of the pre-defined errors, e.g.: cause-error 'Script 'invalid-type function! (see system/catalog/errors for more). – earl Jun 25 '14 at 8:59