1. holding onto opaque string binaries until you need to compose them all together at the end (i.e. the way Erlang's iolists work)
and 2. applying encapsulated operations over a given string (e.g. "split", "scan with regular expression") rather than trying to index into it;
I've never understood why we can't just use a really inefficient-but-practical string encoding, e.g. UTF-32, with a very low-cost stream compression algorithm on top.
That's right. We could figure out how to represent common characters with less bits. For low-cost character stream compression, byte granularity should work nicely with computers, so we should it. A byte could have a bit that indicates the range is extended, like highest bit, because it's easy to process. Same idea could be chained to represent full range of unicode characters. That way we'd have compressed UTF-32 characters!