4
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ out: append out delimiter => append out delimiter \$\endgroup\$ – Graham Chiu Jun 20 '14 at 0:24
2
\$\begingroup\$

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 ","

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – HostileFork Jun 24 '14 at 19:23
  • 1
    \$\begingroup\$ 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). \$\endgroup\$ – earl Jun 25 '14 at 8:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.