Should MAP-EACH Always Return A Block?

I was trying to do something in JavaScript, going character by character and replacing text in a string. JS strings are immutable so it was kind of annoying.

It made me wish to be able to do a MAP-EACH on a TEXT! and have the body return either a NULL, a CHAR!, or a TEXT!... and then produce a TEXT! from the output:

>> map-each c "123" [
     case c [
         #"1" ["one"]
         #"3" [#"T"]
     ]  ; don't have any match for 2, so fall through with null
]
== "oneT"

Not only does that seem useful, but it seems a bit wasteful to always be producing a BLOCK! from a large text string, then merging it together.

But perhaps the argument made against /INTO applies here too...that producing a block is the more generic option, that lets you choose your coalescing step. (Did you want SPACED, UNSPACED...something more complex?)

Certainly blocks are the most generic option.

Maybe I'm not seeing the whole picture, but I feel like PARSE is the proper/default domain for string processing.

Your intuition is likely right here, that since strings aren't tokenized the way blocks are, it will be a rare occasion where you want to map character-by-character to either nothing, another character, or a series of characters. You're more likely to be looking for patterns like "&nbsp", where this kind of code doesn't get you remotely what you want:

 wtf: map-each [amp n b s p semi] str [
     all [
         amp = #"&"  ; only first char guaranteed set :-/
         :n = #"n"
         :b = #"b"
         :s = #"s"
         :p = #"p"
         :semi = #";"
     ] then [
         space
     ] else [
         unspaced [amp :n :b :s :p :semi]  ; up to 6 other chars
     ]
 ]  ; crazypants: only replaces   every 6 characters...

Having a window jumping N characters ahead across text is only going to probably come up in programming puzzles. The case I had actually was N=1, though, which did come up in practice...and you can imagine it accruing state or making some other decisions for some scenarios.