Hacker News new | past | comments | ask | show | jobs | submit login
Ruby mistakes (gist.github.com)
108 points by caludio on Jan 17, 2013 | hide | past | favorite | 90 comments



While there are a couple of things in there that annoy me about Ruby, the list basically feels like a stereotypical American pointing at a stereotypical Frenchman and saying, "Look at that French guy. He looks funny."

Yes, different languages do things differently. I like discussions about the tradeoffs. I get a lot out of discussions like that.

But these WTF sort of lists are pretty worthless. The implicit argument is that OMG this language is broken. Yet for any major language, people have been able to build some pretty complicated stuff with it. Clearly all these WTF things are at the very least surmountable. And a lot of them stop seeming so WTF if you actually make some effort to think about or learn why they're there.


Ruby is not a "broken" langage. I use it and like it.

But yes those are WTF things, and you have to know what they are. I remember that upon encountering it I couldn't wrap my head around throw/catch and raise/rescue. "Surely it can't that bad" I told myself. It was.

The same goes for the so-called "module system". Spitting on java is very hip, but its module system is way better than Ruby is.

And to the other commenter, yes those are mistakes. Calling them mistakes does not imply that the langage is beyond hope or unusable.


I have absolutely no clue what you're talking about WRT throw/catch and raise/rescue being confusing and bad. It's not. They do different things and have different purposes. I can't think of any time that I have used throw/catch, but I absolutely know its purpose, and its parallel nature to exceptions make frameworks like Sinatra fairly elegant.

Ruby also doesn't have a so-called "module system", so I have no clue what you or the OP are talking about.

Both classes and modules in Ruby can be used for namespacing purposes. Modules tend to fill the default "pure namespace" role, but that isn't their purpose—it just happens to be convention. The purpose of a module is an interface (per Java) with implementation—a mixin.

If, however, you're referring to the way that ruby programs are structured and how Kernel#require works…you're still barking up the wrong tree.

Yes, Ruby has mistakes—these two items that you've mentioned are not. Neither are most of the things that the OP's gist mentions. Some things on the list are confusing (break/next/return semantics; Yehuda Katz has a good article highlighting it that I found after a discussion with Reg Braithwaite [raganwald] and others on Twitter that had started the night before at a bar); most of them are simply the author's ignorance—and I don't mean that disparagingly. I am ignorant of many things about Python and JavaScript, because they are not my primary programming languages.

Ignorance can be easily cured. Arrogance is much harder to cure.


By "module system", I mean a way to handle namespacing. I checked a few facts out, and it's actually less bad than I thought. The main problem I see is that it doesn't warn you of collisions when including modules. But if you catch a collision yourself, you can resolve it.

About throw/catch and raise/rescue. First, why have throw/catch and not have it do what it does in every single other langage? I guess this is debatable.

Secondly, a problem with throw/catch is that there is no "clean" way to know if you exited via a throw or normally (the hackish way involves a boolean variable). I guess you could argue that it isn't the intended use. I'm missing this because of the following point.

My real grief with raise/rescue is that it acts exactly like Java's throw/catch, while it could be so much more. Why couldn't we throw arbitrary objects? And pass arbitrary objects to rescue, which would have a special method (lets call it "catches?(e)") that would check if the objects "matches" a thing that was thrown. Exception classes would have this as a class method, and it would match its own instances.

This, incidentally, is exactly how the ruby case statement works. In that case, the special method is "===". So that's an inconsistency in the langage right there I'd say. Or at the very least, a missed opportunity.

So ok, maybe not "mistakes" if that's too strong of a word for you. But "things that should change"? Definitely.

Also, ad hominems are lame.


1. You shouldn't be including modules without knowing what you're including.

2. throw/catch isn't an exception handling system. You do have a clean way to know whether you exited via throw or normally; see http://rubylearning.com/blog/2011/07/12/throw-catch-raise-re... for details (it's a nice little summary). The problem is that you seem to think that throw/catch is for exception handling because everyone else uses those terms for exceptions.

My understanding is that throw/catch (while used less often) is cheaper to set up than begin/raise/rescue/ensure/end.

3. raise/rescue does behave like Java's throw/catch in that it's explicitly for exception handling. As far as throwing arbitrary objects, let me share a beauty from C++:

    try
    {
        throw 0;
    }
    catch (...) // The only thing that catches 0.
    {
    }
C++ doesn't restrict what objects can be thrown. It's a horrendous disaster for the language. Exceptions—including stack unwinding, etc.—are hideously expensive (and they're worse in Java with checked/unchecked exceptions) and should be used only for the worst possible things.

You want raise/rescue to work like throw/catch. If you want that, use throw/catch and save raise/rescue for real exception handling. Or even ignore both of them.

I think that what you've described are neither mistakes, inconsistencies, missed opportunities, or things that should change.

4. I didn't use an argument from ad hominem. I carefully deconstructed the argument on technical merits and noted that these sorts of arguments come from ignorance—of which I have in spades on many topics. I also noted that it is harder to swallow the arrogance that deems arguments from ignorance as "mistakes". Both of these statements are factual.

True story: like many people who discovered Ruby after having used a number of different languages, I had substantive amounts of ignorance about why the language was what it was. In 2002—having just picked up the language, I suggested that nil should act like NULL in SQL (e.g., only tested through special case handling). This was ignorance. It was also arrogance to suggest this for a language that at the time I had been using for less than a month.

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/...

I thought I argued the point very well and looking at the follow-ups, it wasn't as bad as I remembered, but the proposal wasn't very…Rubyish. (Going back through to find a few other examples, I found this gem <http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/...; and I can think of several cases, including the original PDF::Writer, where I had to do just that.)

It is harder to correct a problem of arrogance than a problem of ignorance. The person who wrote the original list was both ignorant (new to Ruby, to be expected, and explicitly Not A Problem) and arrogant (labelling them as mistakes with Ruby as opposed to decisions/behaviours that weren't understood). There's a lot that I don't understand about Python, but I am (at this point, past 40) no longer so arrogant that I declare the explicit self or the way that len(x) is implemented to be mistakes in the design of the language (even though I think they are warts that make Python much less pleasant for me to use).


If it had been a "Ruby WTF's" list, I wouldn't have had any issues with it. While many of those items are like that for good reasons, I'd understand them causing someone reasonably new to Ruby to get confused, and worth discussing.

When it's called "Ruby mistakes" on the other hand, it just gets silly.


100% agree. Bashing on other languages, as the author has done, makes him look childish, but worse, does the community a disservice.

Professionals should be able to articulate and compare the merits of the decisions made by different languages. Even when the talking about more subjective topics, like syntax aesthetics, we can do better than simply calling them "bizarre." These are learning opportunities, not reasons to ridicule "the other team".


> Encoding system is beyond broken

I disagree. Due to some political issues[1], in Ruby's country of origin, Unicode is still not as widely used as it could be. Instead, local multibyte character sets are the norm.

Normalizing to Unicode internally (like what Python 3 does) is impractical or not feasible as turning that Unicode data back into the original string is not reliably possible but might be required.

Ignoring encodings all together and treating everything as byte strings (like 1.8.7 did) might be ok if you live in an english speaking country and you're not dealing with EBDIC. In all other cases, this means that you can't safely use any of the string manipulation built into the language.

As such the second best way to deal with encodings is to have a distinct type for every possible encoding you support and have them incompatible with each other.

Then the applications can decide what they want to do: Not deal with encodings at all (if you live in pure-ASCII land), unify everything to UTF-8 (or 16), or use whatever local character set the input data is in.

The only thing I would be willing to argue might have been a wrong decision is adding the force_encoding method as more often than not calling that is not what you want, even though it might look as if it was. It has a huge shooting-your-own-foot potential.

OTOH, sometimes libraries have mistakes or lie willingly at which point it might be a handy, albeit really dangerous, tool to have. Just like the -f flag for rm. Use it responsibly.

1) http://en.wikipedia.org/wiki/Han_unification


call it "cultural" instead of "political" and you're on the mark.

force_encoding! is sometimes needed if you get data from inputs that pretend to be a different encoding than they actually are. It does offer a lot of rope to hang yourself with though.


It's a bit of both. It's cultural insensitivity at the time it was done that resulted in the better part of a decade where Unicode was stalled in the East.


> You can change the encoding of a string. Just jesus christ wow.

erm, wat?

Edit:

To refute some of the points:

> Why on earth does defined? return a string?

Why not? Strings are perfectly valid in boolean context.

> Using blocks for looping and callbacks

again, wat?

> break/next/return semantics in blocks extremely bizzare

This is a somewhat valid point. Well, don't do it then, if you don't know what you're doing!

> And they have different behavior in procs vs. lambdas

> Mutable objects have a hash method and can go in Hashes. Why!!!

Why not? It is possible to shoot yourself in the leg, but otherwise a very useful feature.

> Special case of flip-flop and regexp in an if statement (only if it appears

> syntactically though!)

> Setting `$=` to something truthy causes all string operations to become

> case-insensitive. This and other magic globals from perl are mind blowing.

Perlisms.

> `f {}`. Tell me what the parsing of that is.

Calling f with empty block.

> Ruby's module system makes namespacing optional (and off by default).

So?

> Regexp with named matches decompose into local variables. Dear lord why.

No they don't necessarily, no.

    1.9.3-p327 :006 > "abcdefg".match(/(?<x>abc)/)
    #<MatchData "abc" x:"abc">
    1.9.3-p327 :007 > x
    NameError: undefined local variable or method `x' for main:Object
> Encoding system is beyond broken

wat?

> Scopes: constants, class vars, instance vars, methods, locals. wtf.

Valid point.

> Constants aren't constant. Truth in naming.

So?

> Thread locals are really fiber locals.

Valid point.

Overall, a very weak rant.


> Why not? Strings are perfectly valid in boolean context.

That's not a good reason for anything, by that yardstick Array#length could return a float and nil#nil? could return an array. And worse, the `?` postfix in Ruby normally means the method returns an actual boolean, not a "boolean context" (convention over configuration only works if the convention is respected). You can't XOR a string and a boolean (no `^` defined on string, although XOR-ing a boolean and a string will work), you can XOR two booleans.

> Why not? It is possible to shoot yourself in the leg, but otherwise a very useful feature.

I'm not sure, but I think he phrased it badly and asks why "interpreter-provided" mutables (e.g. Array) can be set as hash key: while "user objects" are also hashable by default in Python, built-in collections and the like are specified as unhashable as the semantics are not really sensible:

    >>> {{}: 1}
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unhashable type: 'dict'
    >>> {set(): 1}
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unhashable type: 'set'
versus

    irb(main):002:0> {[] => 1}
    => {[]=>1}
    irb(main):003:0> {{} => 1}
    => {{}=>1}


>And worse, the `?` postfix in Ruby normally means the method returns an actual boolean, not a "boolean context"

That's not true. Predicates methods returns Truthy or Falsey values. Not necessarily booleans. See http://blog.leshill.org/blog/2012/03/25/a-question-of-truth....


To be fair, this was one side of that argument. A lot of people adopted the boolean convention, not the truthy one. And the few examples like "defined?" and "nonzero?" really seem more like quirks than conventions. In five years of doing Ruby, those examples built into the language and the Rails shitstorm are the only places I've seen this truthy convention adopted.

Original intent aside, if your language is prided on convention, then it has to be open to the convention changing.


One of the quotes in the linked blog post is the pretty much official statement of the languages creator. defined? and nonzero? are core language methods and unlikely to change. The fact that many ruby programmers have a misconception about what predicates were supposed to imply is sad, but won't change things. In short: All ruby devs need to learn how predicates were intended or they're in for a surprise.

Btw: The mantra "convention over configuration" is a rails mantra and not a ruby mantra.


I'm expecting core methods to change either, as that would undoubtedly break programs. Incidentally, this would be kinda funny because the argument about your program being written poorly if it fails with such a change would be turned on its head. But, in any event, to pretend that Ruby was a perfectly designed language and couldn't possibly have warts is weird.

Also, I never said "convention over configuration," since there's nothing to configure here. I was talking specifically about the convention of what a "?" should return. In that same quote Matz also says that predicates typically return a boolean value, but it's not required. That seems to both imply and endorse a convention.


> That seems to both imply and endorse a convention.

Sure. The convention endorsed is "should, but not required". That's exactly what happens. A pattern I see often is something along the lines of

   def ssl?(url)
     url.match /^https/
   end
which returns nil in the case the url doesn't start with https and a matchdata object if it does.

   jruby-1.6.8 :092 >      def ssl?(url)
   jruby-1.6.8 :093?>        url.match /^https/
   jruby-1.6.8 :094?>      end
    => nil 
   jruby-1.6.8 :095 > ssl? "https://google.com"
    => #<MatchData "https"> 
   jruby-1.6.8 :096 > ssl? "http://google.com"
    => nil


I think we may be in more agreement than either is letting on. However, in most arguments on this matter, the "should" part seems to just get ignored.

Now in that whole Rails hoopla, it turned into "it's not required and neither defined? nor nonzero? do it," ignoring the whole "should" part. And now people are pointing at Rails as another example, reinforcing their own bias.


I agree with you that it might be nice if you could actually rely on it, but OTOH I have never personally encountered an error caused by a non-boolean predicate.

However, it's not only nonzero? or defined? that don't return boolean values. see http://news.ycombinator.com/item?id=5074676 for more examples. If you read through the core libs documentation you'll find more examples. You just cannot rely on predicates returning true/false in all cases, so you either have to learn not to rely on it at all or learn every example where it doesn't. So just don't rely on it.


I have long (6+ years) used truthy values from my predicate methods, and rarely coerce them into explicitly true or false values. (There are times when it's necessary, but these are extremely rare.)


I'd say that justifying it as because you can still determine the truthiness of a string is missing the point; defined? (http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-d...) can return one of a number of string values, or nil, depending on what expression you pass to it. The actual return value can be useful to you depending on what you're doing, or you might be happy enough just accepting that there it returns a value other than nil (which is to say you don't care what it is, just that yes, something is defined which matches the passed expression).


> defined? (http://ruby-doc.org/docs/keywords/1.9/Object.html#method-i-d...) can return one of a number of string values, or nil, depending on what expression you pass to it. The actual return value can be useful to you depending on what you're doing

While the actual return value can be useful, it would be just as useful if the method had a more sensible name than hinting it's a predicate, which it is not.


What would such a more sensible name be? I look at this as a trade-off between code readability and consistency. Particularly when assuming it is a predicate (without greater knowledge of the method) is not a dangerous assumption, I'd say the trade-off is a good one.


token_type or lexical_type would make more sense to me. On top of defined? not returning a boolean, it has other confusing semantics. In particular, it's the only keyword I can think of that appears to exhibit call-by-name semantics. I don't know if this is just a special parser case, but it doesn't evaluate your arguments despite looking like a method call. So the return value can be doubly confusing.


It's a special parser case. The reason is that defined? may be followed by an uninitialized constant:

  jruby-1.6.8 :089 > defined? Foo::Bar
   => nil 
  jruby-1.6.8 :090 > Foo::Bar
  NameError: uninitialized constant Foo::Bar
You don't want the argument to be evaluated because that could raise a NameError. It's actually an operator.


Thanks for the clarification. I was recently surprised by the return value when:

z = x.y

z == x.y # => true

defined?(z) == defined?(x.y) # => false


what about defined_as

(i agree that within ruby's conventions (weak typing) defined? is not that bad)


> ruby's conventions (weak typing)

No. "Weak Typing" doesn't mean jack shit, and there are very few under its thousands of different and incompatible identities which Ruby would match, save for the very least useful of them.


no. weak typing is exactly what i mean.

> if a.defined?

works, because the returned string type is implicitly converted to a boolean. that's weak typing. at least one (i guess the most common) interpretation of weak typing.


because ruby does not have a separate tuple data type, so it's trivial to do so

    data[[v1, v2]] = something
and because strings are mutable in ruby.

If you couldn't use mutable objects as hash keys a bunch of things would become _way_ harder.


> If you couldn't use mutable objects as hash keys a bunch of things would become _way_ harder.

    data[[v1, v2].freeze] = something
meh. Hell, Hash could even freeze its parameters on entry if they're not already frozen.

Meanwhile if anybody gets a hold of the key and happens to mutate it (not necessarily for nefarious purposes, just because it solves they problem) you can quite literally lose your pair:

    irb(main):001:0> h = {}
    => {}
    irb(main):002:0> l = [1, 2]
    => [1, 2]
    irb(main):003:0> h[l] = 1
    => 1
    irb(main):004:0> h[l]
    => 1
    irb(main):005:0> l[1] = 3
    => 3
    irb(main):006:0> h[l]
    => nil
    irb(main):007:0> h[[1, 2]]
    => nil
    irb(main):009:0> h[[1, 3]]
    => nil
    irb(main):010:0> h
    => {[1, 3]=>1}
    irb(main):011:0> h[[1, 3]] = 3
    => 3
    irb(main):012:0> h
    => {[1, 3]=>1, [1, 3]=>3}


lost your object? rehash to the rescue!

  jruby-1.6.8 :068 > h = {}
   => {} 
  jruby-1.6.8 :069 > l = [1, 2]
   => [1, 2] 
  jruby-1.6.8 :071 > h[l] = 1
   => 1 
  jruby-1.6.8 :072 > h[l]
   => 1 
  jruby-1.6.8 :073 > h[l] = 3
   => 3 
  jruby-1.6.8 :074 > h[l]
   => 3 
  jruby-1.6.8 :075 > l[1] = 3
   => 3 
  jruby-1.6.8 :076 > h[l]
   => nil 
  jruby-1.6.8 :077 > h.rehash
   => {[1, 3]=>3} 
  jruby-1.6.8 :078 > h[l]
   => 3
see http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-rehash :

  Rebuilds the hash based on the current hash values for each key. If values
  of key objects have changed since they were inserted, this method will 
  reindex hsh.
On one side I must admit that the behavior is a bit surprising I haven't ever encountered in the wild. It's not that easy to get hold of a hash key at a random point in the code where you don't know that it is a hash key.


Most of the answers to your answers could be summarised with "principle of least surprise makes sense". That's why most/all methods ending with an "?" should return a boolean, mutable objects should warn/fail if used in context where immutable is expected, string encoding should not be possible to override, constants should be constant, things called threads should be threads...

Otherwise things are not consistent and confusing. Yes, with enough time you just learn all the tricks and start fighting for the status quo, bacuase that's how things always worked and it's ok. But is it?...


Changing the encoding of a string effectively destroys the data inside. It's entirely useless and potentially very dangerous.

Block-based loops in Ruby are pretty nice, but the semantics could be simpler. Rust gets this right.

Hashable mutable objects are a giant liability. Ruby even has freezing of objects, so it should only allow hashing frozen objects at least.

Ignoring scope by default is extremely stupid and dangerous. Namespacing is pointless if it's off by default.


> Hashable mutable objects are a giant liability. Ruby even has freezing of objects, so it should only allow hashing frozen objects at least.

Well, of all dangerous things you can do with ruby, this is one of the least dangerous ones. How about "any gem can monkey patch any other gem or even core classes"? Isn't this a giant liability?

Besides, it's not that difficult to make your custom hash that only allows immutable objects for keys.


Indeed it is, but at least that liability is much more obvious and publicised.


> Changing the encoding of a string effectively destroys the data inside. It's entirely useless and potentially very dangerous.

Says you. For my part, I've more than once had to deal with data that the source tells me is in one encoding, but where I can't trust the encoding and have to use heuristics to determine whether or not to switch encoding after I've read the string in.

Yes, there are other ways of handling that than changing the encoding, but that doesn't mean it isn't convenient to be able to correct the encoding value already attached to the string.

As for the "potentially very dangerous" I don't really buy that. Yes, you can turn the string into complete gibberish if you call it without knowing what you're doing, but it's not exactly something that you're likely to call by accident.


If you don't know (or trust) the encoding, you have bytes, on which you can do whatever heuristics you wish, then decode into a string. It's much safer than changing the encoding on an existing string.


How in the world is it any different? #force_encoding is not meant to mutate the data. The entire point of it's existence is to be able to correct a wrongly set encoding.


If you don't know the correct encoding of a sequence of bytes, then you don't yet have a string, just a sequence of bytes. You can decode the bytes into a string, or encode a string using a particular encoding into bytes.

Bytes != strings.


> Changing the encoding of a string effectively destroys the data inside.

You are assuming the string had the right pair of <encoding type, byte value> which is not guaranteed.

For example, an old mysql binding wouldn't handle some of the encoding settings correctly, so you'd get a UTF-8 string with attached data in latin-something.

This could be trivially fixed in client code by simply calling #force_encoding.


That is a bug that is more easily (and less dangerously) fixed by asking for bytes and then decoding them from latin-1.


And yet, in 99.5% of use-cases, it's infinitely more convenient not to have to jump through that hoop.

I've been a Ruby developer for… seven years now, and have literally never encountered actual problems in the real world by this, by mutable hash keys, or well over half the other issues on this list. And I'm willing to bet the vast majority of other developers haven't either.

Ruby has mostly sensible defaults that work as expected in most practical cases.


>>Setting `$=` to something truthy causes all string operations to become case-insensitive. This and other magic globals from perl are mind blowing.

>Perlisms.

They're incomplete Perlisms that's the real flaw because in perl these special variables are dynamically scoped.

NB. $= does something completely different in perl. I don't know of any perl special variable that replicates what Ruby's $= does.


I always wondered about the defined? thing too.

A rubyism is that any function suffixed with a ? should return a true/false, but then again, another rubyism is that everything that isn't explicitly False or nil evaluates to True, so it kind of works out. I can see how this would make anyone coming from another language kind of scratch their heads, though.

You'd expect defined? to return simple boolean, not evaluate the type of object being referenced (kind of like .class)


No, your conception of methods suffixed with ? is false. Methods suffixed with ? are expected to return a truthy/falsy value. nil and false are perfectly falsy in comparisons and any string will evaluate to true. So returning the type of the object in question and nil in the false case is perfectly valid and expected. It's not the only method to work that way.


As stated elsewhere in the thread, this is a clever retort but in practice defined? is the only major example I've seen where the ? suffix results in a non-boolean return value. You're arguing over a convention, and I think most people writing Ruby would be surprised to learn defined? returns a String.


It's a common misconception that ruby developers think that predicates must return a boolean value. It's still a misconception. This blog post linked elsewhere in the discussion is a good summary: http://blog.leshill.org/blog/2012/03/25/a-question-of-truth....

Another notable example is nonzero? which returns the value of the number if nonzero and nil otherwise:

  jruby-1.6.8 :097 > 1.nonzero?
   => 1 
  jruby-1.6.8 :098 > 0.nonzero?
   => nil 
See also File#world_writable? [1], File#world_readable? [2], File#size? [3], Kernel#autoload? [4], Encoding#compatible? [5]

If you rely on predicates always returning true/false you're in for a nasty surprise.

  [1] http://ruby-doc.org/core-1.9.3/File.html#method-c-world_writable-3F
  [2] http://ruby-doc.org/core-1.9.3/FileTest.html#method-i-world_readable-3F
  [3] http://ruby-doc.org/core-1.9.3/File.html#method-c-size-3F
  [4] http://ruby-doc.org/core-1.9.3/Kernel.html#method-i-autoload-3F 
  [5] http://ruby-doc.org/core-1.9.3/Encoding.html#method-c-compatible-3F


`defined?` is actually an operator: http://www.ruby-doc.org/docs/ProgrammingRuby/tut_expressions...

That may make things actually more confusing on first glance, but it does give a technical reason why it doesn't follow method naming conventions.


defined? behaves completely correct in respect to naming conventions. It's behavior is notable in a different way: It's one of the few places where undefined constants can appear in the code without raising an exception:

  jruby-1.6.8 :089 > defined? Foo::Bar
   => nil 
  jruby-1.6.8 :090 > Foo::Bar
  NameError: uninitialized constant Foo::Bar


I don't have an issue with most of these. In fact, to me, "ruby allows you to do X, and it shouldn't" is an annoying line of thought. If I want to make my language dance, I don't want it to cry-- I want it to sing along.

There's one that I'll agree with, however, and that's "Inline rescue, no ability to specify what exception."


Short version: "Ruby isn't Python!! wat?!?"


Not "wat?!?" but "why!!!". It's not a question, it's an exclamation of pure shock so intense that incredulity can not even register.


Using blocks for looping (i.e. using callbacks) is actually a great feature. See Oleg Kiselyov's spirited defense of callbacks (as opposed to iterator objects) here: http://okmij.org/ftp/papers/LL3-collections-enumerators.txt

The fact that `break`, `next`, and `return` work the way they do in blocks is a remarkable simplification; it turns a set of constructs that work only on loops into functional constructs that work on functions, broadening their usefulness.


    Using blocks for looping and callbacks
      * break/next/return semantics in blocks extremely bizzare
Actually they're pretty sensible (return is anyway, break/next are a bit weird but no more so than the rest), the main issues with blocks is the — mentioned — Proc-v-lambda dichotomy and the — not mentioned — block-not-being-first-class issues. Although it's hinted at with further mentions of magical behaviors surrounding blocks.

Blocks, in and of themselves, are a very good base for implementing flow control.


The break/next/return behavior is not sensible at all. Anybody that comes to ruby with knowledge of other languages with first-class functions will be confused by it, in particular return.

Having said that, once you learn the difference it's not that big a deal.


> The break/next/return behavior is not sensible at all.

They are. Especially return.

> Anybody that comes to ruby with knowledge of other languages with first-class functions will be confused by it, in particular return.

Not necessarily, and especially not if they happen to understand what "block" means, and why Ruby uses "block" not "lambdas" for its core.

Or if they have the slightest inkling of knowledge of Smalltalk and/or Self.

And either way, unless they've already fossilized the difference is easy to learn (and solves important issues in imperative languages)


A return statement in a block is more or less a call to an escape continuation. So is break. Most programmers don't know what an escape continuation is, though once explained can use them. Even if they did, those escape continuations are captured implicitly. There's no reason to believe that people are just going to divine their meaning. And if they're new to Ruby, they're not going to understand what a block is, pretty much by definition. It's a feature that's pretty unique to Ruby.


> A return statement in a block is more or less a call to an escape continuation.

So?

> Most programmers don't know what an escape continuation is

Not that they have any reason to care.

> There's no reason to believe that people are just going to divine their meaning.

I don't think I ever said that.

> And if they're new to Ruby, they're not going to understand what a block is, pretty much by definition. It's a feature that's pretty unique to Ruby.

It's not for any known value of "unique". Blocks are present in most C-like languages, and first-class blocks were introduced by Smalltalk.


My favourite weird Ruby thing (is on the list, anyway) is that CONSTANTS can be changed, but it raises a warning... Of course, you can also set up a mutable CONSTANT (a hash, for example) and change it without a warning.

So, my point is, why are they called CONSTANTS?


For the same reason private methods are called private, even though you can still access them with `send`. Ruby does not allow one programmer to handcuff another; it only allows you to put up warning signs. "I don't expect you to redefine this reference, and you shouldn't depend on the stability of this internal method."


"Ruby does not allow one programmer to handcuff another..."

That's not a valid argument.

"Q: Why does your OS allow any program to write anywhere in memory?"

"A: Because we believe in freedom. Our profound belief is that the OS should not allow to handcuff programmers. They must be free to do anything. They should be able to write anywhere in memory and anywhere to disk"

How do you want people to be able to be able to reproduce state and to reason about a program if f*cking CONSTANTS can be modified!?


>> That's not a valid argument.

It's not an argument, it's a description. That is, in fact, the way Ruby generally works.

>> Q: Why does your OS allow any program to write anywhere in memory?"

That's not an accurate comparison. A user installing two programs cannot be expected to ensure that they play nicely together. A programmer can be expected to ensure that an application's code and libraries play nicely together - especially in a non-compiled language, where you always have the source code.

Even more especially where you have a community that generally promotes the best libraries and the developers who make them.

>> How do you want people to be able to be able to reproduce state and to reason about a program if f*cking CONSTANTS can be modified!?

I'm not proposing a crazy new system; I'm describing a language that has been quite successful. In actual practice, I have never, ever had a bug that was caused by a constant being redefined. I've never accidentally redefined one, and if, hypothetically, I did, I would see a warning.

It might seem like a problem, but it isn't.


Reducto ad absurdum! We're not talking about the same thing with full access to anyone's memory here. We're talking about boundaries that if you have control over your software should be handled the way you want anyway.

The type of flexibility that Ruby allows make it a really interesting language. In 99.999% of cases people never use all these features and it's considered bad form to do so.

Ruby is one of those languages that depends a lot on cultural queues to work so well but unlike other languages it doesn't enforce everything on you. It's a tradeoff and it is by no means better than other languages, just different and pleases certain developers more than other languages.


Well said. And s/Ruby/Perl/ and this same statement still holds true.


Of course it is a valid argument. You just don't like it. That's your right. But claiming the argument is invalid is just dumb.

Your OS analogy is also poor. There's about two decades worth of discussions about memory protection in AmigaOS and it's spiritual successors (AROS, MorphOS etc.) for example, and while some people involved with those are strongly in favour of adding things like memory protection (though it is hard to do without severely breaking compatibility), there are also a lot of people for whom part of the appeal with these OS's still is that a user can hook into anything and everything:

For AmigaOS there are user-level applications that can replace the task (process) scheduler, for example. There are user-level applications adding virtual memory. There's a library that gives any user-level application free reign to more easily manipulate the MMU. There OS itself provides API calls to allow user applications to replace the code used for ever library call (system wide - the equivalent of being able to override syscalls in Linux...). There are assemblers with explicit support to muck around with hardware registeres and OS structures. There are applications designed to trace everything that happens at the OS level. And a lot of these capabilities are in fairly common use by the (admittedly few) users of these systems.

You might argue (and I'd agree with you) that this isn't a great basis for a modern OS. But that does not make it an invalid stance - most of these people don't want what we consider a modern OS. Many of them instead want something where they can do exactly those kind of things with nothing like a userland-kernel barrier to deal with etc.


"My favourite weird Ruby thing (is on the list, anyway) is that CONSTANTS can be changed..."

That and relying on hashes made from mutable thing...

Taking a note to never ever try Ruby


My ideal language would include: the intuitive-expressive power of ruby, the "explicit is better than implicit" mantra of python, the functional structure of clojure, the malleability and interactivity of lisp, and the unobtrusive static typing of go.


Well, he prefers Python (https://github.com/alex). Perfect, I'll keep making my rubies shinier :)


> Well, he prefers Python

More to the point, he comes from Python.

He's also — I believe — working on a ruby (ish?) implementation (or something along those lines), which probably colors his view of what is or isn't annoying. Most of what he lists makes the life of an implementor a living hell.


Ruby makes a number of tradeoffs in favor of "expressibility" at the expense of readability (without a _high_ degree of ruby proficiency) and ability to reason about programs. You can disagree with these tradeoffs, but they're not mistakes. It also has some things which might be designed differently if the language started from scratch (e.g. string encoding), but I would still say its a stretch to call these mistakes.

Ruby also has a number of things which make it "hard" (some would say impossible) to create fully equivalent alternative implementations. This is suboptimal, but it is what it is; at this point the level of change required to make it amiable to alternative implementation would be...prohibitive.

I do agree, however, that the inline rescue with no ability to limit to anything other than StandardError was a mistake...I've just seen way too many cases where _far_ more than reasonable got caught. That said, I'm not sure there would be any great way to add matching on the inline rescue to the grammar -- I think it would have to be removed.


I don't get it... I thought ruby was supposed to be a mess like this? its one of its greatest strengths...


I was in the hopes to find a deeper analysis on the problems encountered by ruby and rails recently.

We get very interesting and obligatory postmortem analysis for every minute a popular site is down, but when a major framework is found to have holes, nobody tells us what could have been done to avoid them, what flaws in the tools our the team's allowed them, and here comes again the elephant in the room.


My first thought: That's a short list!


If you enjoyed this, I recommend the Unix-Haters Handbook:

http://en.wikipedia.org/wiki/The_Unix-Haters_Handbook

(I love Unix, and C, and Ruby, and yes, they're all awful, festering examples of "worse is better".)


This list would be a lot more helpful with some description of why he thinks these are mistakes.


a rant without a solution to the rants... sry but why this post is voted up so high? do we wane start a ruby vs python war? both languages are good in there own ways. I don't even understand how someone can compare theme...

"Look this Car is better then your Pineapple"


Na, Ruby and Python are quite close to each other and quite comparable. Which other language would you compare it to?


Python and Ruby have somewhat comparable syntaxes, but Objective-C and Ruby are much more comparable in overall language design. If you feel the need to compare similar languages, it may be a better choice as a starting point. Or Smalltalk, but I suspect it wouldn't resonate quite as well, as much fewer people are familiar with it.


I would be interested in seeing something on common Rails mistakes.


— Original poster should have basic social skills but DOESN'T. Worst poster ever.


"Mutable objects have a hash method and can go in Hashes. Why!"

This is indeed terrible. It is broken beyond repair and it's probably because they thought Java doing something similar with their hashcode() method was "smart".

It is not that mutability is "evil". It is that contracts provided by the language / APIs on top of mutability definitely is evil, because it's impossible to obey / respect the contracts.

It is probably one of the biggest source of hard-to-find / hard-to-reproduce / hard-to-debug bugs.

The problem goes way further than that in Java that said: the very presence of "hashcode()" and "equals()" at the very top of the hierarchy (in the Object class) is fundamentally broken. Even if you are using immutable objects in Java, you are still deeply fcked if your classes are non-final. Even if you classes are* only allowing to create immutable objects and are final, you are still deeply fcked if you use composition.

Because it is impossible* to extend a class (or use composition) and satisfy the hashcode/equals contract.

This was explained in "Effective Java" back in the days.

Now it may even be possible that this very equals/hashcode/mutability SNAFU is, unconsciously for a lot people, at the heart of the current backlash and hatred towards mutability.

The fact that it is so broken (and provably broken: the contracts CANNOT, as in RFC2119, be respected) may be the reason while functional programming is making a comeback.

It certainly is for me and I'm not looking back.


"Extremely complex grammar, makes barrier to entry for implementation much higher"

Compared to... what?


The average, or even more-complex-than-average, alternative language. He's right, the grammar is a complete pig.


Example? I don't understand what is meant by this.



One of my favorite things about Python 3 is that the grammar actually got smaller: http://docs.python.org/3/reference/grammar.html

It's only three lines, but still.


It seems that Ruby uses GNU Bison:

http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/parse.y?vi...

It is not so clear for documentation purposes, but grammar rules are in between 850-4988 lines.

File/Line size does not tell much about Grammar complexity, but anyway, the whole parse.y file is bigger than cpython/Parser directory. BTW, Ruby's parse.y file is just the input to Bison.


When he says the complex grammar is a barrier to entry for implementation I think he is referring not to the difficulty of writing code in Ruby, but to the difficulty of writing a Ruby interpreter.


What is meant is that Ruby is difficult to parse compared to most languages. By comparison Lisp is easy to parse




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: