Skip to content

Comment on Ruby mistakes

Comments

> 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
AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.