Live data from Hacker News

A rant about Ruby 1.9 String encoding

github.com

41–50 of 55 posts

Re: A rant about Ruby 1.9 String encoding

#41

Earlier quoted context omitted.

I'm not a Ruby-head, so maybe I don't have the necessary context to understand what's going on, but: As far as I'm concerned, the only time I care about encodings is when I'm trying to do I/O. I'll always have to choose an encoding when engaging in I/O, explicitly or implicitly. While I'm shuffling strings around in memory, I expect it to behave like a sequence of unicode code points. If the implementation wants to a…

I also thought the exact same thing, but I know that encoding stuff is so complex that I may be missing something. For what its worth, I believe Cocoa does things this way: a string is an abstract object of unicode code points, when its time to read or write you must choose an encoding.

The Java and .NET infrastructures make that choice for you too. I guess I wasn't there when Java first got started, it's possible that there was a backlash[1] against this decision by the people who are using encodings that aren't subsets of unicode. I don't get to see many "enterprise" systems, but I get the impression those applications where it's important to use some kind of not-unicode are sufficiently rare that they're best served by specialty libraries. You can't make everyone happy with one solution, but if a simple solution works for 99%, then I'd call that a roaring success.

[1] a backlash other than the OMG-2-bytes-per-ASCII-character hysteria, which is irrelevant for these purposes, they could just as well have chosen UTF-8

Re: A rant about Ruby 1.9 String encoding

#42
post #35

I know nothing about the author, but there are some statements made that suggest that the author hasn't had to deal with the wild-and-woolly reality of encodings out there in a lot of extant data. One only wishes that all data were UTF-8. What Ruby 1.9 gets absolutely right is that its String implementation is completely encoding agnostic (by which I specifically mean that it doesn't force your data to be encoded in…

The roundtrip thing is an edge-case that doesn't really justify inflicting the non-deterministic pain on everyone. Python 3 and Java have taken the 'one true internal encoding' path and while hardly free of warts, it's an approach that is practically saner. The alternative is making some people's hell everyone's hell, forever.

"Hardly free of warts" doesn't even begin to cover the pain that's dealt with if you have to deal with these external encodings.

And, if you've got loads of data in an encoding that doesn't roundtrip, it's hardly an edge case.

Ruby's implementation is supposed to be such that if you want UTF-8 support and know that your (text) inputs and outputs are always going to be UTF-8, you never have to think anything differently than you did in Ruby 1.8. If it isn't working that way, then I think there's a bug.

Re: A rant about Ruby 1.9 String encoding

#43

Earlier quoted context omitted.

The technical reason is that it's a stupid idea. Not all encodings can be safely round-tripped through UTF-8, which means you can end up losing some data. (Consider http://homepage1.nifty.com/nomenclator/perl/ShiftJIS-CP932-M... as a quick example: "Actually, 7915 characters in CP-932 must be mapped to 7517 characters in Unicode. There are 398 non-round-trip mappings.") Loss of text data is bad.

Are these encodings common enough that they're worth supporting in the basic String class? Surely another class could be provided to handle these edge cases, they don't have to be natively handled. There's no need for any data loss to occur - the String class would merely not support converting from non round-trippable encodings.

If they're not natively handled, you can't regex them. If they're not natively handled, you can't convert any numbers that might be in them to numeric values.

Yes, they're common enough (especially in Japan) and encodings have to be baked deeply in if you really want to use everything that a Rubyist expects to be able to use.

Re: A rant about Ruby 1.9 String encoding

#44
post #22

Earlier quoted context omitted.

If I've understood the rant correctly, Ruby (1.9?) confuses byte streams with strings. My understanding was just the opposite: now that strings are associated with encodings, he can no longer assume that a1 + a2 results in a string with the same encoding as a1 and a2, since a1 and a2 can have different encodings.

The implication is that the programmer must know what is used internally, though, because things will go horribly wrong otherwise. That's a sign of a very leaky abstraction - if you need to know the format and behaviour of the underlying byte stream, you'd probably be better off using the byte stream directly, the abstraction is just introducing uncertainty where it should be isolating from such concerns.

It's also supposed to be an incorrect implication for Ruby most of the time. It should be possible to do a single encoding trivially (e.g., if all of your data is shift_jis or UTF-8), but rather than trying to (badly, and usually unsuccessfully) hide the encoding difficulties from the programmer if you've got mixed encodings, Ruby has chosen to be a little more up front about encodings.

The reality may be a bit differently, but I recall seeing an email message from Matz on ruby-core last year suggesting that it was supposed to be trivially easy to work with one encoding (specifically mentioning UTF-8, but implying others).

Re: A rant about Ruby 1.9 String encoding

#45

Extrapolating, the same rant applies to all duck-typed languages: the number of possible outcomes for a=b+c explodes depending on types and contents of a,b and c, therefore his first assumption about one dimensional space is incorrect. This is why most C++ teams prohibit their members to overload operators.

Precisely. This sounds like an argument for the superiority of static typing more than anything.

Re: A rant about Ruby 1.9 String encoding

#46

Extrapolating, the same rant applies to all duck-typed languages: the number of possible outcomes for a=b+c explodes depending on types and contents of a,b and c, therefore his first assumption about one dimensional space is incorrect. This is why most C++ teams prohibit their members to overload operators.

Only if your static types for strings include a seperate type per encoding. Most languages including the ducktyped just use a single encoding internally "utf-8" for example. So the String type is always compatible. For Ruby though it sounds like the string type could be any encoding. That to me sounds dangerous. It's not caused by the language being ducktyped. It's caused by the language having the same type with multiple behaviours. Ruby made strings into a mine field for 99% of programmers and safer for 10%

Not sure that's a good ratio.

Re: A rant about Ruby 1.9 String encoding

#47

I know nothing about the author, but there are some statements made that suggest that the author hasn't had to deal with the wild-and-woolly reality of encodings out there in a lot of extant data. One only wishes that all data were UTF-8. What Ruby 1.9 gets absolutely right is that its String implementation is completely encoding agnostic (by which I specifically mean that it doesn't force your data to be encoded in…

There is no need to make an internal encoding comply with utf8 or any standardized encoding since it is internal. The point is that the existing solution simply doesn't work out well.

I personally won't upgrade to 1.9 if they don't fix that. Even with simple code snippets the ruby 1.9 solution has caused too much pain to even consider it as an eligible option. I personally rather switch to groovy or python than ruby 1.9. The way Ruby 1.9 handles encodings sucks. Period.

BTW the author has written on that subject several times and he nows it quite well.

Re: A rant about Ruby 1.9 String encoding

#48

It seems the most obvious solution is to store strings in a standard encoding (say UTF-8) and to always convert strings to it at the time of their creation. Is there a technical reason why Ruby doesn't do this?

And performance... My non-utf8 1 GB file whose number of lines I need to count thanks Ruby 1.9 for not uselessly and forcefully making an in memory utf8 copy of it.

Re: A rant about Ruby 1.9 String encoding

#50
Rants about strings and character sets that contain words of the following spirit are usually neither correct nor worth of any further thought:

  > It's a +String+ for crying out loud!  What other
  > language requires you to understand this
  > level of complexity just to work with strings?!
clearly the author lives in his ivory tower of english language environments where he is able to say that he "switched to UTF-8" without actually really have done so because the parts of UTF-8 he uses work exactly the same as the ASCII he used before.

But the rest of the world works differently.

Data can appear in all kinds of encodings and can be required to be in different other kinds of encodings. Some of those can be converted into each other; some Japanese encodings (Ruby's creator is Japanese) can't be converted to a unicode representation for example.

Also, I'm often seing the misunderstanding that "Unicode" is a string encoding. It's not. UTF-(8|16) is. Or UCS2 (though that one is basically broken because it can't represent all of Unicode).

Nowadays, as a programming language, you have three options of handling strings:

1) pretend they are bytes.

This is what older languages have done and what ruby 1.8 does. This of curse means that your application has to keep track of encodings. Basically for every string you keep in your application, you need to also keep track what it is encoded in. When concatenating a string of encoding a to another string you already have that is in encoding b, you must do the conversion manually.

Additionally, because strings are bytes and the programming language doesn't care about encoding, you basically can't use any of the built-in string handling routines because they assume each byte representing one character.

Of course, if you are one of these lucky english UTF-8 users, getting data in ASCII and english text in UTF-8, you can easily "switch" your application to UTF-8 by still pretending strings to be bytes because, well, they are. For all intents and purposes, your UTF-8 is just ASCII called UTF-8.

This is what the author of the linked post wanted.

2) use an internal unicode representation

This is what Python 3 does and what I feel to be a very elegant solution if it works for you: A String is just a collection of Unicode code points. Strings don't worry about encoding. String operations don't worry about it. Only I/O worries about encoding. So whenever you get data from the outside, you need to know what encoding it is in and then you decode it to convert it to a string. Conversely, whenever you want to actually output one of these strings, you need to know in what encoding you need the data and then encode that sequence of Unicode code points to any of these encodings.

You will never be able to convert a bunch of bytes into a string or vice versa without going through some explicit encoding/decoding.

This of course has some overhead associated with it, as you always have to do the encoding and because operations on that internal collection of unicode code points might be slower than the simple array-of-byte-based approach.

And whenever you receive data in an encoding that cannot be represented with Unicode code points and whenever you need to send out data in that encoding, then, you are screwed.

This is a defficiency in the Unicode standard. Unicode was specifically made so that it can be used to represent every encoding, but it turns out that it can't correctly represent some Japanese encodings.

3) Store an encoding with each string and expose the strings contents and the encoding

This is what ruby 1.9 does. It combined methods 1 and 2: It allows you to chose whatever internal encoding you need, it allows you to convert from one encoding to the other and it removes the need to externally keep book of every strings encoding.

You can still use the languages string library functions because they are aware of the encoding and usually do the right thing (minus, of course, bugs)

As this method is independent of the (broken?) Unicode standard, you would never get into the situation where just reading data in some encoding makes you unable to write the same data back in the same encoding as in this case, you would just create a string using this problematic encoding and do your stuff on that.

Nothing prevents the author of the linked post to use ruby 1.9's facility to do exactly what python 3 does (of course, again, ignoring the Unicode issue) by internally keeping all strings in, say, UTF-16. You would transcode all incoming and outgoing data to and from that encoding. You would do all string operations on that application-internal representation.

A language throwing an exception when you concatenate a Latin 1-String to a UTF-8 string is a good thing! You see: Once that concatenation happened by accident, it's really hard to detect and fix.

At least it's fixable though because not every Latin1-String is also a UTF-8 string. But if it so happens that you concatenate, say Latin1 and Latin8 by accident, then you are really screwed and there's no way to find out where Latin1 ends and Latin8 begins.

In todays small world, you want that exception to be thrown.

Conclusion

What I find really amazing about this complicated problem of character encoding is the fact that nobody feels it's complicated because it usually just works - especially method 1 described above that has constantly being used in years past and also is very convenient to work with.

Also, it still works.

Until your application leaves your country and gets used in countries where people don't speak ASCII (or Latin1). Then all these interesting problems arise.

Until then, you are annoyed by every of the methods I described but method 1.

Then, you will understand what great service Python 3 has done for you and you'll switch to Python 3 which has very clear rules and seems to work for you.

And then you'll have to deal with the japanese encoding problem and you'll have to use binary bytes all over the place and have to stop using strings altogether because just reading input data destroys it.

And then you might finally see the light and begin to care for the seemingly complicated method 3.

Sorry for the novel, but character encodings are a pet-peeve of mine.

Post reply on HN