The best kittens, technology, and video games blog in the world.

Wednesday, July 12, 2006

List of things that suck in Ruby


As Bjarne Stroustrup said: "There are only two kinds of programming languages: those people always bitch about and those nobody uses". Well, let's bitch about Ruby. And not about lame things like performance or libraries, but about expressiveness.

  • It's not possible to change class of an existing object. Now, why the heck would anybody want to do that ? It is actually quite useful - you use object factory to create user interface widgets from XML, determining their look, and then move them to subclass to determine their behaviour. Without this we have to use some ugly tricks. evil.rb lets us do that in some cases, but not with UI widgets. Languages where it works: Perl.
  • Ruby has really nice and concise syntax with one nasty exception - one needs to write end almost all the time. Relying on whitespace instead would make the code look much nicer. Languages where it works: Python, Haskell.
  • Keyword arguments to function are not ordered. foo :a => 1, :b => 2 is identical to foo :b => 2, :a => 1, and that's often not what we want. Languages where it works: Perl, PHP.
  • irb doesn't remember command history between sessions. Languages where it works: Octave.
  • Most libraries use Java-style constants with huge namespace prefixes instead of real Ruby symbols. So we have Gtk::Widget::TEXT_DIR_RTL which evaluates to some number instead of :rtl which would simply be converted to number on call (actually it's converted to magical object, not to a number, but that makes little difference). This makes many APIs a lot harder to remember and a lot less natural. And adding Enums to Ruby would be going in completely wrong direction. Languages where it works: it sucks in all languages I know, but that's no excuse ;-).
  • Symbol is not Comparable. I don't care what's the order between :foo and :bar, but there should be some. Without it, it's impossible to sort structures that contain Symbols (to get canonical representations etc.), and that seriously limits Symbol's usability. Languages where it works: Prolog. It does not work in Scheme or Common Lisp.
I think all these issues are reasonably easy to fix.

18 comments:

Zach Beane said...

(string<= 'BAR 'FOO) => t

Symbols are string designators, so string sorting works fine for symbols.

taw said...

This is technically true, but the thing that I'm expecting from a language is for every object to know how to compare itself with other objects of the same kind, and for this comparison to naturally extend itself to composite data structures. In Ruby I can do [[1,"foo"], [2,"bar"]].sort() and so on. In Python I can do sorted([(1,"foo"), (2,"foo"), (1,"bar")]).

In languages without it (or in which implementation of it is broken), like Perl, C++ or OCaml, I have to mindlessly code those comparison functions and explicitly use them. Now if you're not much into object-orientation you may think it's a very small issue, but I consider it a rather serious conceptual problem.

Extending comparability to Symbols in Ruby is just a few lines and I think it's just an oversight that it's not in the standard library:

class Symbol
include Comparable
def <=>(x)
to_s <=> x.to_s
end
end

As far as I know neither Common Lisp nor Scheme have any generic<= function (<= is only for numbers). Would it be possible to code it ? I really don't know much about CLOS and Lisp generic functions, so maybe I'm simply missing it.

Zach Beane said...

Lisp doesn't cater to that expectation (I don't think it's particularly natural).

taw said...

This is object-oriented expectation - if we (on conceptual level) treat comparing as a method, then the thing I want is perfectly natural.

On the other hand if we (again, on conceptual level) consider comparing a function then it doesn't make that much sense.

So I think it just depends whether you're into object-oriented programming, or not really.

Anonymous said...

So what are the RCR numbers?

http://www.rcrchive.net/

taw said...

I submitted one that seems easiest to fix:
RCR 342: Symbol should be Comparable

The one about massive symbolification would require a proof of concept code that it doesn't break backward compatibility in reasonable use cases. I think I'll RCR it later.

piggy said...

hmm, as for the ugly 'end', Matz once said he did some experiment to eliminate 'end' in early version of Ruby, but the syntax turned out to be too hard to parse. Maybe Ruby's syntax is way too flexible. :^)

I don't agree with you on keyword arguments, since I would have to remember the sequence of arguments without such feature, and therefore would require an IDE providing intelligent argument hint.

taw said...

Well, end-less syntax was just a wishlist item, I'm fully aware that it would probably require severe backwards compatibility breakage.

Now as for keyword arguments, I might have expressed myself a bit vaguely, but the point is not enforcing a particular order (most functions do not care, nor should they), but being able to get the order information if the function wants to. It is occasionally useful in DSLs.

Example - defining a lexer using a set of regular expressions:
str.lex /foo/ => proc{blah1},
/bar/ => proc{blah2},
/./ => proc{warn "Funny character, ignoring"}

It looks very pretty this way, unfortunately the function has no way of knowing that /./ should be tried last :-). On the other hand this is much uglier:
str.lex /foo/, proc{blah1},
/bar/, proc{blah2},
/./ , proc{warn "Funny character, ignoring"}

There was some talk on Ruby-talk on hashes remembering order of insertions, and they would make syntax like that possible. It is not a big deal, it's just that Perl has it, and losing features when moving to a new language sucks, even if the features aren't that big :-)

piggy said...

I see your point, which makes sense indeed. Thank you very much for the clarification.

Thomas Hafner said...

Taw, are you also missing a feature for enforcing a local variable scope (like done by keyword "my" in Perl or "local" in Lua)?

f = lambda do
v = 1
g = lambda do
v = 2 # Oops!
3 * v
end
[v + g[], g[] + v]
end
puts f[]

In the line commented by "Oops!" variable v is unintentionally shared with the one of the outer lambda.

taw said...

Local variable scoping in Ruby sucks, but not a single language got it right yet.

Ruby scoping is quite decent, with a single exception that block arguments are not automatically locally scoped, and it's very easy to make a silly mistake. They plan to fix it in Ruby 2.

(0..1).times{|i|
(2..3).times{|i|}
# Will output 3 3, not 0 1
puts i
}

Ruby still beats all other languages I know, as far as local scopes are concerned. Python has no real nested scopes, and most other languages require explicit declaration for each local variable, what is absolutely abhorrent.

And seriously, in your example, how could you expect v not to be shared ? It is obviously the same v, it's very hard to imagine making such a mistake unintentionally.

Cullen said...

To get IRB to remember state across sessions, see here:

http://blog.nicksieger.com/articles/2006/04/23/tweaking-irb

Cullen said...

re: It's not possible to change class of an existing object...

Technically no, but you can just use singleton classes for most things.

widj = Factory.new($some_xml_in)
...
Later we discover we want widj to be (for instance) a modal dialog:

class << widj
  include ModalDialogBehavior
end

Or at least it seem to me that something like that would be a workable scheme.

Cullen

黄毅 said...

The first one "change class of an existing object", python can do that too.

obj.__class__ = Klass

taw said...

黄毅: Ruby can do that with non-standard evil-ruby library. I'd much rather have it as an official supported feature, like in Python and Perl.

nano said...

Check Wirble for irb related stuff like history between session and syntax coloring.

Phlogistique said...

"It's not possible to change class of an existing object." I think you would love to use a prototype-oriented language, like http://iolanguage.com

For history between sessions in ALL languages, see rlwrap.

Anonymous said...

Significant whitespace would cripple Ruby. There are several drawbacks, the biggest in Python is that it doesn't allow for multiline lambdas.

It is the biggest reason why Python is so rigid.