"Runes" were the original name, as implemented in Plan 9 by the same folks, for what the standards committee later decided to call the relatively blaze term "Unicode codepoints"--and which are not quite the same thing as characters.
(In fact, I would say that the notion of a Unicode "character" is ambiguous to the point of uselessness--there are glyphs composed from several codepoints (base glyph + combining accents), which should be treated as one "character"; there are ligatures that hold single codepoints, but which semantically are multiple "characters"; there are stacking languages where one "character", representing a whole word, will be composed together from several codepoint "radicals"; while in other ideographic languages, each pre-composed idea-part is its own "character" and has its own codepoint; and so forth.)
The solution is to use Normalization Form C (NFC) (which combines accents with characters).
> there are ligatures that hold single codepoints, but which semantically are multiple "characters"
OK, so use Normalization Form KC (NFKC) (which splits ligatures, and combines accents with characters).
You're right that "length" of a unicode string is very ambiguous. Arguably, you shouldn't be able to call "length" without supplying an argument about what you are actually asking.
No it's not, it takes 10 minutes to learn that && means and and || means or (maybe a little longer to get the hang of it properly), and this knowledge transfers to many programming languages.
(This is a little like arguing "we shouldn't use + when English has a perfectly good word 'add'"; symbol reasoning is valuable.)
Python expresses the bitwise and and or using the & and | characters, the exact same characters as Go.
And that also explains why Go chooses to use && and || for and the logical and or operators.
No. Cognitive overhead. You pay for it every time you parse these words in your brain. You pay for it by reducing the number of nested/combined clauses that you can parse on the fly.
(This is far from the only readability issue with Go, by the way, and you're right in that it's among the more superficial ones. The language is designed so well in all ways except the one that matters the most, it hurts.)
&& is pronounced "and" but actually means "shortcircuit left-to-right-evaluated and".
If you're coming from a Pascal (or non-programming) background, you do not assume either left-to-right evaluation order, nor short circuit evaluation.
The cognitive overhead is always there, because whether you like to admit it or not, programming is applied math, and exact meaning is very important;
e.g.:
if a == 0.0 or b/a > 3 then launch_missile();
Without the "cognitive overhead of knowning guaranteed left-to-right + short circuit", this code is wrong.The hypothetical "newbie programmer who can write a working program but has cognitive overhead deciphering &&" is a mythical creature that does not actually exist.
len("нєℓℓσ") // 12, because there are 12 bytes
utf8.RuneCountInString("нєℓℓσ") // 5, plz kill me i am an abomination
I'm not sure I understand your objection. Bytes and UTF8 characters are different things, and you can't abstract away the difference. There are also times, perhaps the majority of times, when you will need the byte count of a UTF8 string. That means you need at least two different length functions for strings and they need different names.Shouldn't UTF8-specific things live in the utf8 namespace? Some programs won't need any string handling, after all, and it would be a waste to include code they never used.
Assuming you can allow the utf8 namespace as sensible, would you feel better if there was a RuneLen() function aliased to RuneCountInString()?
If you are that upset about it, then my suggestion is to explain your rationale and submit a patch[1] to provide the alias. It's not like it would be hard to code. Perhaps you might convince people and get it in the next release.
But that doesn't matter. Let's say you are correct: when working with strings, you more often want the rune length. It still wouldn't be the right decision, given the other design decisions of Go, because it would have needlessly complicated things with only arguable benefits. Let me show you what I mean.
The len() function works with a whole lot of things: strings, arrays, slices, maps and channels. For the first three, len() returns the number of bytes involved. This is because all three are backed by an array, and so sensibly have similar semantics. It would have violated the principal of least surprise for anyone who knew the language to have an array-backed storage not return a byte count. Both the language developers and the users of it would have to special-case strings, in code and in their brains.
Now, they could have decided to do it anyway, but then another surprise awaits. What happens when you take a slice of a string? Oh no, more special casing and more complication for everyone.
The Go developers do special-case where doing so would clearly be a win for their users. Consider range, which iterates by runes over a string, potentially moving the index on the underlying array forward by more than 1 on each pass. That is clearly going to be the most common usecase the user is going to want and so was worth doing. It also eliminates many of the usecases where getting the length of a string in runes would matter to you. Not all, but a lot.
What happens when you slice a unicode string in Go is that it cuts multi-byte characters right in half, unless you get the byte boundaries just right. I know real programmers keep the byte boundaries for all the chars in all their strings in their head at all times, but for people like me this basically makes string slicing unusable for non-ASCII text.
Python somehow magically slices unicode strings without chopping characters in half.
In Python:
s = "нєℓℓσ"
s[1:4] # "єℓℓ"
In Go: s[1:4] // "�є"You need a byte offset to slice a string, and it's impossible to convert from a Unicode rune offset to a byte offset without parsing the entire string up until that point. I'm not all that familiar with Python, but if the language works as you implied, it is basically doing this behind the scenes in common string processing tasks:
1. The user uses some kind of pattern matching function or whatever to find where they want to split the string. Python returns a rune index.
2. The user tells Python to go split apart that string along a rune index. It promptly begins parsing the string all over again until it finds the right byte boundaries.
3. The language then actually creates the new string in between the byte boundaries.
Sure, a Python implementation could statically optimize this, but... why should it have to in the first place? That's fucking stupid and should be considered a language bug when it could be doing this: 1. User pattern matches blah blah blah and gets a byte index.
2. User tells their sane language to split the string apart at the byte index and it just does so.
>I know real programmers keep the byte boundaries for all the chars in all their strings in their head at all timesWhen the hell would you have to remember the byte or rune boundaries for characters in the first place? Why would you be slicing up a string with magic number indices? If you're getting indices from pattern matching functions, you shouldn't care whether they're in bytes or bits or nibbles, you should just be passing them on to your language's split routines (or whatever else you wanted to do). Unless, of course, you're the one actually writing low-level string processing routines, in which case rune offsets are far less useful than byte offsets for the reason explained above.
This Python "feature" seems to exist entirely to keep newbies from getting confused when they attempt to slice up strings in their REPL, for I cannot fathom a reason why anyone would write "s[1:4]" in production code. IIRC, Python was designed for pedagogy, so I'm not surprised that it would take on such a pointless implementation cost just to spare teachers from explaining why "s[1:4] gave me question marks"
This might explain why my comments seem like heresy to you. I would point out that the OP is about Python programmers switching to Go.
Ok, so it (EDIT FOR YOUR BENEFIT: I'm talking about slicing strings with rune indices here, not slicing in general. Array slicing is a useful language feature, and, uh, it's not unique to Python or anything) is not just a language wart, but a fertile source of pointless inefficiency in everyday Python code, glad to know.
>This might explain why my comments seem like heresy to you.
You aren't challenging my beliefs or anything, I'm just trying to make you see that you don't understand how UTF-8 string operations work very well. If you did, you'd understand that Python is doing the exact same thing as Go here, but in a less efficient manner.
The fact that it's used all the time would suggest it's not pointless inefficiency, no? Maybe you should try Python before bashing it.
>Python is doing the exact same thing as Go here, but in a less efficient manner.
It's not doing the same thing. "�є" is not the same as "єℓℓ".
Dealing with a format where data elements are defined to be fixed length in characters that happens to be encoded in Unicode?
Well, that rather depends on what you mean by "character" and "in half".
>>> s = u're\u0301sume\u0301'
>>> print s
résumé
>>> len(s)
8
>>> for i in xrange(len(s)):
... print s[i]
...
r
e
s
u
m
e
>>> print ' '.join(s)
r e ́ s u m e ́I have and I generally like the syntax to be more explicit (since I so rarely work in bytes): string.getBytes().length
You make all fair points, and I guess it is my opinion that varies then but I think the rule of least surprise would involve returning the rune count for both strings and string slices. Also, wow, range iterates over runes but len returns byte count. That's messed up.
The implementation of strings in Go is a 2-word struct containing a pointer to the start of the string and the length (in bytes). Under this implementation, len(s) is O(1) and RuneCountInString(s) is O(n). It makes sense to have the default case also be the fast one, particularly since people appreciate Go for its performance.
Alternatively, you could store the rune-count in the 2-word struct to reverse the above runtimes. However, this is detrimental for the common operation of converting between []byte/string as well as writing a string to a buffer. Both of those operations are a simple memcpy with the actual Go implementation, but would be O(n) using this alternate implementation.
Perhaps you could make it a 3-word struct that contains both byte-length and rune-length; Then all strings take up additional memory as well as requiring more overhead when used as function arguments.
Consider a simple string: "école". How many runes does it contain? Possibly five:
LATIN SMALL LETTER E WITH ACUTE
LATIN SMALL LETTER C
LATIN SMALL LETTER O
LATIN SMALL LETTER L
LATIN SMALL LEtTER E
Possibly six: LATIN SMALL LETTER E
COMBINING ACUTE ACCENT
LATIN SMALL LETTER C
LATIN SMALL LETTER O
LATIN SMALL LETTER L
LATIN SMALL LEtTER E
If you normalize the string you can guarantee you have the first form, but not every glyph can be represented as a single rune.Fortunately, you generally don't need to deal with any of this. If you're working with filenames, for example, you really only care about the path separator ('/' or '\' or whatever); everything else is just a bunch of opaque data. You can write a perfectly valid function to split a filename into components without understanding anything about combining characters. When you're dealing with data in this fashion, you rarely if ever care about the number of runes in a string; instead you care about the position of specific runes.
In Go:
fmt.Printf("%s", string([]rune("нєℓℓσ")[1:4]))
// єℓℓ
In Python: print("нєℓℓσ"[1:4])
# єℓℓ- If you are passing a number of related arguments, often a struct is a better data structure than default or named parameters.
- I am ambivalent about panic/recover, but have no problem with the rest.
- I do tend to agree that "and" and "or" would be more readable, but it's such a minor issue.
- Regarding your UTF-8 example, I imagine the Go authors believed most Go users would be spending more of their time dealing with bytes than characters. Go is not a language optimized for text manipulation, it is optimized for byte manipulation, like C. This is apparent in the lack of effort put toward optimizing the regular expression engine to date.
This also means your program code is simpler, and therefore, faster.
Function overloading usually means virtual method tables, and therefore indirect method calls. Depending on how deep your inheritance / overloading structure is, these vtables can get really messy.
(I had a class in university where we were given a C++ UML class diagram, and told to draw the vtables that resulted when one instance of a subclass was instantiated.)
PROGRAMMER SEES INTERNAL REPRESENTATION
foo(int a, char b) foo_int_char
foo(int x) foo_int
- Function overloading is a major convenience that you will miss. There are differently named versions of every function and you will call the wrong version with the wrong arguments all the time. The number of functions in the standard library could be reduced by at least 1/4 if they'd got this right. The official FAQ (http://golang.org/doc/faq#overloading) explains that leaving out overloading is "simpler", meaning simpler for them.
- Default parameters are a major convenience that you will miss. Using strings.Replace() to remove some chars from a string? Don't forget to pass the -1 at the end, asshole! The -1 says don't put a limit on the number of replacements. In Python there would be a max=None default parameter and this would never bite anyone.
- No named arguments, because fuck readability.
- Forcing me to handle errors is great. Having 20 different ways to do it is not great. Examples: fmt.Errorf(), fmt.Fprintf(os.Stderr), errors.New(), log.Fatal(), log.Fatalf(), log.Fatalln(), panic/recover...
- Using && and || for logical operators in this day and age is just ridiculous. Why do people keep inventing programming languages as if Python doesn't exist?
- Don't think that just because the Unicode guys invented Go that Unicode is going to be easy. Their solution is not to create an airtight abstraction layer between chars (or "runes" WTF?) and integers. Their solution is to provide almost no abstraction and force you to deal with the inherent integer-ness of all characters. Example:
In Python:
In Go: tl;dr If you're inventing a programming language for human beings (not UNIX gods), try it out on a group of smart high school students first. It will be a humbling experience.