4 ms·
Ruby's Range class has plenty of bugs to offer if you use it with anything except numbers, especially if you use any of its more "exotic" features. I guess thi
by jeffparsons 2y ago
Ruby's Range class has plenty of bugs to offer if you use it with anything except numbers, especially if you use any of its more "exotic" features.
I guess this isn't super constructive, but to me the whole thing smells of not just a lack of discipline, but a lack of _interest_ in correctness that seems to be endemic in the Ruby community.
We ended up writing our own `TimeRange` class to paper over the base Range bugs that show up if you use it with times.
One awkward takeaway from the experience is that I've come to believe that sometimes it _is_ worth unit testing other people's code, counter to the popular advice.
- t-writescode 2y agoIt's worth noting that there's a good chance many of these issues are well known by the Ruby community and many even get frustrated by them; *but*, so much code now relies on its behavior, predictably or otherwise, that they couldn't change it. My pet frustration in Ruby is `.slice` behavior. > "abc".slice(0, 10) "abc" > "abc".slice(2, 10) "c" > "abc".slice(3, 10) "" > "abc".slice(4, 10) null In Python, the last would also return "". edit: updated the code to reflect a basic repl, without using puts or the 'or' statement, which handles the falsy behavior that "" and null both have in Python.
- jeffparsons 2y ago> My pet frustration in Ruby is `.slice` behavior. I would argue that any input that doesn't have exactly one obviously correct output should result in an exception being raised. But Ruby often shies away from doing that. My interpretation is that they prefer to return _something_ rather than blowing up, so the show can go on. I guess I sit at the opposite end of the philosophical spectrum from Ruby, so it's no wonder choices like this frustrate me. My personal philosophy is "explode early, explode often", because propagating invalid states only leads to future pain and suffering. > [...] so much code now relies on its behavior, predictably or otherwise, that they couldn't change it. I'm not denying this is a reason for keeping the questionably behaviour, but it's kind of funny because Ruby also introduces subtly backward-incompatible changes semi-regularly, including in "minor" releases. (Maybe that last bit is unfair, because I don't think that Ruby has ever claimed to follow semver.)
- Terr_ 2y ago> My interpretation is that they prefer to return _something_ rather than blowing up, so the show can go on. I think I saw this horror movie, but it was titled "PHP." :p
- sqeaky 2y agoI agree it isn't entirely obvious at first glance why slice behaves the way it does, there is a pattern. If the first number in the range is a valid index into the string then you'll get something back, and if it isn't you can't get anything back so you have to get back a nil. Coming from a C++ background my time in Ruby was weird and enlightening, it was less specified than C++, but pretty much everything is. I don't think it was underspecified, but a lot of the behavior was meant to be intuited and follows some kind of underlying pattern. If you miss the pattern you miss a lot of the functionality of that part of the language. I agree that failing early and often is generally preferable. I don't see that Ruby doesn't do that, it just has more definitions for generic behaviors. Consider the amount of things in the range from the original post that produce type errors. Last time I checked both the C++ language and the C++ standard Library both had about twice as many pages in The Standard than all of Ruby in its standard Library have, yet there is still undefined and implementation to find behavior. So there are places where we get weird an unexpected Behavior that can result in bad State being passed around even in very formally specified places.
- wavemode 2y ago> If the first number in the range is a valid index into the string then you'll get something back, and if it isn't you can't get anything back so you have to get back a nil But, 3 is not a valid index into a string of 3 characters. By your criteria, "abc"[3] correctly returns nil. Whereas "abc"[3,1] returns "" The language designers really just did not think this through.
- lloeki 2y ago> but Ruby often shies away from doing that. My interpretation is that they prefer to return _something_ rather than blowing up, so the show can go on. There are two (intertwined) explanations: - In Ruby exceptions are costly to generate. - Similar to Go, Ruby's stdlib design favours using return values for signalling, keeping exceptions for exceptional cases. So in plain† Ruby there are a lot of places that use `nil` to signal that something's off, e.g `[1,2,3][4]` returns `nil` because it's an out of bounds access, or `"123".downcase!` returns `nil` because nothing has been downcased, or `__FILE__` returns `nil` when a file is `eval`'d. So yes, you have to check for things like `nil` (or explicitly swallow with `&.` which is kind of a Maybe monad pattern) before doing stuff with values, just like you check for `if err == nil` in Go. Running a type checker like `steep` helps a lot linting for these cases. † Rails likes to generate exceptions, but Rails != the whole of Ruby.
- marcianx 2y agoI immediately see the logic in this API. When slicing, I look at indexes as being between elements or at the start (0) or end (length). This gives an in-bounds starting index between 0 and length, inclusive. So if the starting index is in bounds, you get a substring. If it's not, you get no result. And your answer for Python is not quite correct: "" is falsy in Python, and both of the last two when translated to Python give "null".
- t-writescode 2y agoApologies, you're correct. I didn't consider the ramifications of printing to the screen with an or. I was trying to get rid of the lack of distinction between `puts null` and `puts ""` in Ruby. And yes, I also understand the logic of the API; but if you're used to using slice to protect against random NPEs and out-of-bounds exceptions - which is something I do and am used to being able to trust in as a general pattern.
- LegionMammal978 2y agoWhat I find weird here is the asymmetry: Ruby apparently allows the end index to be out of range, but not the start index. Contrast with, e.g., Rust's slice syntax, where both endpoints have to be in range, or else it will cause a panic.
- randomdata 2y ago> Ruby apparently allows the end index to be out of range, but not the start index. What gives you that impression? "abc".slice(4, 10) is perfectly valid and accepted, assuming the code above is accurate.
- sqeaky 2y agoUnderneath the hood that's a C string, and the four points to the null terminator, so it's indexable. And that's why you get an empty string if you point exactly one past the end. That's why if you put five or more in for the first index it fails to produce a result entirely. I think I might I preferred an exception or a failure code being returned, but I can't say the current design is truly awful.
- codesnik 2y agobut because of sane notion of falsiness in ruby, it's easy to both always have a string on unchecked inputs "abc".slice(4, 10) || "" _and_ check those inputs in one go if needed. substr = "abc".slice(start, len) raise "wrong inputs" if substr.nil? just that one feature (only nil and false are falsy) of ruby makes it positively stand out among all the languages that have "null" as concept
- JonChesterfield 2y ago> sometimes it _is_ worth unit testing other people's code, counter to the popular advice Relying on a library you don't have any tests for puts a lot of faith in it working. Also in it continuing to behave as you expected when you change the version. People have weird ideas about other people's code though. Something being found on GitHub may mean it doesn't need it be reviewed or even glanced through before putting it into production while code written in house is obsessed over.
- randomdata 2y agoPeople in the software industry are just weird in general, it seems. I regularly see the same type of behaviour around blog posts by random authors drawing "My, what a fantastic idea, we must try this!", even when it clearly isn't a good idea, but anyone with ideas internally are automatically discounted.
- stonemetal12 2y ago>Relying on a library you don't have any tests for puts a lot of faith in it working. To be fair Range is part of the language implementation. If you have no faith in the language implementation, why are you using it?
- jaynetics 2y agoWhat are some of the bugs when using Range with Time?
- randomdata 2y ago> I've come to believe that sometimes it _is_ worth unit testing other people's code, counter to the popular advice. The popular advice is that you shouldn't test implementation, only interface behaviour. Which means that you shouldn't explicitly test the usage of someone else's code within your code. Their code is just an implementation detail. If the implementation is faulty, testing of the interface should still reveal that faulty behaviour. That doesn't necessarily mean you shouldn't ever test someone else's code (with their interfaces). But if someone else's code is not tested it is undefined, and relying on undefined behaviour is fraught with problems. It is unlikely you would want to use it in the first place, which, practically speaking, leaves little justification to invest in improving the situation.
- chucke 2y agoClaiming "plenty of bugs" without stating any, doesn't really open the thread for constructive discussion, and frankly sounds like unnecessary FUD. Claiming that the ruby community doesn't care about correctness is borderline offensive, at least without evidence that reports are being ignored.
- chubot 2y agoI've come to believe that sometimes it _is_ worth unit testing other people's code, counter to the popular advice. I've never heard that advice I basically always unit test the code I depend on -- to learn how it works! --- libc in particular is worth unit testing. C has lots of "creative" APIs that are easy to misuse. Like functions that return the same static buffer over and over again. I think the only way you find that is by running tests with ASAN enabled ... libc functions also have so many error conditions, and they may differ between platforms.