There’s a lot of features of Ruby that I like, but there are some that just drive me nuts like blocks not taking blocks and the ampersand operator. Raganwald did a great job of explaining blocks, procs, and the ampersand in this blog post: Explanation of Ruby’s Unary operator. I came away with the feeling, “Wow! It took that much explanation just to tell how to send blocks around?” If blocks were first-class citizens, Ruby would be more elegant. Raganwald would not be writing huge blog posts on block vs. proc because it would be unneeded.
I feel for Blaine, I really do. When I first looked at Ruby, quite honestly I hated it. Not hated it in a Blubb-dacious “It’s not the same as my popular language” way, but hated it in a “The language is full of Accidental Complexity” way.
To my eyes, Ruby as a language looked a lot like an internal IT app that is built as an aggregation of features. There might be a wonderful, coherent design in the implementation that I can’t see, but the interface I use seems like a bunch on one-off features that don’t play well together.
There’s a part of my personality that craves purity and regularity in a system. Purity and regularity have practical benefits, to be sure, but at the same time there are practical benefits to being messy and having special cases optimizing for things people do frequently. This is why design is freakin’ hard. It’s not as easy as following a bunch of rules that produce a system with the smallest possible intellectual surface area, otherwise we’d have stopped with Scheme and its five axiomatic special forms.
A Rube Goldberg contraption does a simple thing (like making ramen) in an extremely complex way. It turns accidental complexity and irregularity into entertainment.
Nor is it as easy as piling more features on regardless of how well they fit or whether people will actually use them. Otherwise Windows would have 97% of the market and OS X 3%. (Oh wait.)
When it comes to design, sometimes you have to experience the result to judge it. You can decide whether a chair is attractive by looking at it, but you really need to sit in it a while to know whether you will feel comfortable using it. Even then, only long experience will tell you whether it is a keeper. Many things that are nice in the showroom are mediocre in day-to-day use.
Design is all about problem solving. What problem do you think programming languages solve as a group? What problem does Lisp solve? Smalltalk? Ruby? For whom?
Matz has said that Ruby is an attempt to solve the problem of making programmers happy. So maybe we aren’t happy with some of the accidental complexity. But can we be happy overall? Can we find a way to program in harmony with Ruby rather than trying to Greenspun it into Lisp?
(I speak as a man working on rewriting code for Ruby: By far the hardest part of this is trying to provide the power of macros in a mechanism that works in harmony with the Ruby Way, rather than bolting Lisp onto Ruby’s side.)
Programming languages also solve the deep problem of helping programmers think about the parts of a program that matter and not clutter their minds up with the parts that don’t matter. This is a very hard problem. Very, very hard. Make things too simple, have too few axioms and abstractions, and you end up with something where each element is extremely simple to understand, but any non-trivial program has too many elements with difficult-to-understand interactions and dependencies so as a whole programs are harder to understand.
Does a regular language help us understand the parts of programs that matter more than an irregular language? That is not an easy question to answer. When I’m struggling with some subtle difference between Proc.new and lambda, I want to shout YES, GODDAMIT, GIVE ME SOME CONSISTENCY.
I’m wary of trying to decide about languages based on infrequent edge cases. With a certain very popular language, I’ve made up my mind that the design choices don’t pay off, that the places where you work hard to express yourself aren’t places where the easy, obvious thing is easy to write and obvious to read. But so far in Ruby, the things that trouble me do seem to have some inherent trade-off merit. I can see how making blocks a special case makes certain eay, simple things easy. So is it overall the best possible design? I don’t know. is it a good design? So far it seems to be a reasonable design.
At my core, I believe axiomatically that there is no one “best” language. That is not an excuse for saying that every language has merit. While I think that there can be more than one good language with different approaches and styles, I do not exclude the possibility that 90% of the programming languages in existence are stinkers through and through.
Is Ruby better for me personally than Lisp or Smalltalk? I don’t know the answer to that question either.
I decided a while back that I would give Ruby an honest try. And while I give myself the freedom to express my misgivings about some of the choices Matz has made, I also try to keep an open mind about them.
It’s really freakin’ (I know, I used this word already) hard for me to to do, but I’m trying to find out if worse might be better. And I don’t mean, “Ruby is worse than Lisp, but it’s better for those n00bs over there.” That’s not embracing change. I mean I am trying to discover if worse might be better for me personally, and it is hard for me to open my mind to that possibility when I’m already invested in “better.”
At this moment in time I have extremely mixed feelings about Ruby. I sorely miss the elegance and purity of languages like Scheme and Smalltalk. But at the same time, I am trying to keep my mind open to some of the ways in which Ruby is a great programming language.
¶ 10:42 AM
Comments on “My Mixed Feelings about Ruby”:
"Matz has said that Ruby is an attempt to solve the problem of making programmers happy."
I think he has succeeded. Let's be honest, ruby as a language is more COMPLEX than php.
At the same time, as a language - and its whole design - ruby is a LOT better than php.
Coding in ruby is fun. I think a few things could be improved, but I am happy with ruby since 5 years by now.
If i compare ruby to other languages, I feel repulsed. The syntax of other languages is mostly horrible. Some languages have an ok syntax (haskell) but are A LOT MORE complex than ruby (!)
Perl is an utter mess.
Python is ok. I dont like some decisions, but python is a good language - dont get me wrong here. I hope ruby and python join forces because they have A LOT in common, much more than i.e. python and java or ruby and java (ever will).
I agree about complexity in one point - it takes a lot of time to "master" ruby. I think, personally, that ruby is a bit too complex. I know that if i would make a new language, I would do away with module vs object vs class and make prototype objects only, including "behaviour" objects where one just "steals" behaviour to get the functionality one wants.
Ruby helped me become a better programmer, by enabling ME to think better. I was a horrible PHP programmer, i still am. But I wrote my IRC bot in ruby, and it worked (it was buggy but it worked).
I never was able to do the same in php, and this was 4 years ago.
'At my core, I believe axiomatically that there is no one “best” language.' I disagree. For me what matters most is syntax. I love that you can omit the () in ruby. People complain about this but I think they dont get the point - it makes the overall code more beautiful to read.
If you dont care what you wrote then maybe stick to perl, but I want to build a whole infrastructure in ruby, and thus I do care about the beauty of code.
'I sorely miss the elegance and purity of languages like Scheme and Smalltalk. But at the same time, I am trying to keep my mind open to some of the ways in which Ruby is a great programming language.'
Maybe we come from different areas. I always liked scripting languages (yes, that includes perl!)
I never liked C or C++ much. And I had no real use for either Scheme or Smalltalk although Smalltalk is very close to ruby in regards to thinking WITH objects (i didnt like scheme syntax)
At the end of the day I think what ruby really will become is the language that will replace perl. And this alone is a big task, for perl still has a lot of old users.
Some things become edge cases because we don't use them often. And some things we don't use often because they're inconvenient. I think there's more potential to blocks, but some patterns were relegated to edge case because of their inconvenience.
I also feel like Ruby is this clobbered-together solution. Maybe because it is.
Would have been better if Ruby was perfectly designed from the start, instead of adding features that make people happy and productive, but don't always mesh perfectly together.
Unfortunately, in real life that doesn't happen, instead we get to choose from patchwork languages, languages that never change, and languages that change in consistent, but inconvenient, ways.
The last two options worked very well for Scheme and Java, but sometimes it's not about how elegant your shoes are, but how comfortably you can walk in them.
I'm still learning Ruby and Rails, and its helping become a better programmer in general. I'm a .Net programmer, and I think its the best thing MS has produced. It's not perfect and leaves a lot to be desired. Monorail is part of the answer to .Net's problems, but there is no perfect solution.
Ruby and Rails are the same. Rails has helped my learn MVC and I'm learning REST now. I see the wisdom in convention over configuration, and why being simple is much better than being elegant. However, this is an area of trouble with Ruby. It can be very elegant to the point where its not clear to a non-rubyist like me what the code is doing.
When a language/platform gives you the ability to be more productive, its a very good thing. There are always trade offs to consider. In Ruby/Rails, its the complexity and performance concerns.
Let's reverse the argument but keep the pragmatic viewpoint: why keep the rough edges when you can slowly smooth them out of the design? For example the proc vs. blocks vs. lambdas vs. ampersand symbols is a necessary piece of cruft (i.e. do people use them in different incompatible ways)? If all the uses could be made compatible why no slowly moving the design in this direction. Another example, turtles all the way down is important, that's why we have a couple of candidates in this direction today, the community/market has spoken, so it's fine to slowly go in this direction.
It doesn't need to be a either/or dichotomy, we can have cruft in phase 1 and design in phase X, every phase in between is a movement to a simpler solution. If you start with careful design you'll be constrained by your own knowledge (as good design is based on constraints) and the less you know about languages the worse are going to be the constraints. It's much better to have no constraints but evolve (evolution is key) than choose the wrong constraints. So Ruby becomes better because the community complains about its warts and shapes the direction of future features.
Actually this dichotomy between design and evolution is very important, because you can have a design that works better than evolution, but if and only if you are really really smart and knowledgeable. It's easier to succeed with evolution, but if you try to control this process the results aren't good. Evolution of a language means killing cruft that hurts (even if killing it hurts) and adding cruft that is good. It also means not falling in the with the language as it is, instead loving it because of what it can become (always looking forward).
Going to the simple vs. complex dilemma, it's easy to pat yourself in the back and say "look how simpler than Blub we are", but this will only make you better than Blub. It's also easy to look at some artificial standard (let's say Scheme, Smalltalk or Haskell (btw all languages that I love)) and complain that our language is broken because it's more complex. It doesn't matter how complex or simple it is today if the language can evolve towards simplicity (or any other artificial standard).
Ruby's future seems uncertain to me: now we have multiple implementations, so it's possible to have different features emerging in different implementations and evolve the language, but this evolution is a dead end if the features can't be easily shared between implementations. Lisp, Scheme, Smalltalk, all suffered from multiple (slightly incompatible) implementations, Ruby can go in this direction too if we aren't careful. OTOH if it successfully manage to let the implementations share good features and deprecate irrelevant ones than Ruby has a good chance of becoming something unheard of: a language with multiple implementations that doesn't hurt the developers.
P.S. People forget that Big Ball of Mud is also an architecture. Perl and Ruby have a great design behind it, it's not because it looks like a big ball of mud that is a less effective design than an "elegant" design.
All the following is just a flame, but I had to say it :)
I have also gave ruby a try and enjoyed it a lot by then. But as I understood later this was due my love to javascript, which is a far greater language.
And here is why: C# added LINQ, Javascript doesn't need them because it has compact enough syntax already. Java now have closures, Javascript has them too. In python everything is an object, in javascript this is also true. In ruby you can create object attributes dynamicaly, the same goes to javascript. On the other side javascript have a very small syntax overhead, what can't be told about C# and even more ruby. It beats on the other hand it comes head to head with ruby expresiveness when needed, what nor python neither C# (vell now it can). The only problem is that it's slow, but we will see who will be the king pretty soon.
Now hear the final part. The best language will appear in few years few years, when every language will be ported to .NET/Java CLR. You just wait...
Reg, I know you allude to this in the sentence "Purity and regularity have practical benefits", but I place more emphasis on them (or you de-emphasize them to make a point this essay).
As I'm sure you're aware, purity and regularity have a huge benefit in terms of making the code understandable.
In your post on news.yc (http://news.ycombinator.com/item?id=232246), you give an excellent example of some confusing ruby code:
"Just for a chuckle, what does this snippet of code do?
p = Proc.new { return :to_sender }; p.call
What about this one?
l = lambda { return :to_sender }; l.call
Why and how do they differ? And given that they differ in behaviour, what do you think this should return? False, right?
c.class == l.class "
For me, I have better things to occupy my neurons with than the difference between lambda {} and proc {} and Proc.new().
I can keep an open mind about design decisions, up to the point where the accidentally complexity becomes a burden.
These days, I don't have to care about lambda{} vs proc{}. All I have to remember is (fn []). I've been playing with Clojure (http://clojure.org/), and I'm really loving it, as a clean, full-featured, fast lisp that runs on the JVM.
Given that other languages cannot do what Scheme can do and are not as elegant as Scheme, perhaps we should have stopped with Scheme and worked on making it better. Perhaps adding features like Erlang-like concurrency. Imagine where we would be today if Sun had championed Scheme instead of Java.
"perhaps we should have stopped with Scheme and worked on making it better... Imagine where we would be today if Sun had championed Scheme instead of Java."
Then we might end up with something like Clojure. I know my last post already plugged it, but seriously, check it out. It's a lisp, neither scheme nor CL, but closer in spirit to Scheme. It has lots of nice concurrency features, like immutable local variables, a parallelized map funcion, and Software Transaction Memory (think (begin transaction) (dostuff) (commit) rather than locking. Oh, and it runs on the JVM, so you have trivial access to any Java library and you can even use Java debuggers.
The author has said he isn't too interested in Erlang style messaging for in-process communication, but he's not opposed to the idea for cross-process communication.
All this talk about languages needs some clarification as to what they are and the problems they try to solve. It's not about making programmers happy, it is to communicate well up and down the tree of development.
Programming languages must solve many problems to be successful. In theory they exist to communicate between people, and I agree with you that this is an important purpose.
However, “In theory there is no difference between theory and practice, but in practice there is.”
In practice languages need to market themselves by appealing to all sorts of feelings and emotional needs like greed, fear, belonging, and exclusivity. In practise languages often must represent evolutionary incremental change in order to foster adoption unless a cataclysmic extinction event (like the creation of a new platform that replaces an old platform) takes place.
Languages have many purposes and fulfill many needs. This does not mean that they do not have a primary purpose, of course, but it also does not mean that we can disregard all other purposes as inessential.
Ruby reflects the learning process of its creator. Imagine the language he would create today in trying to achieve the same goal, but starting with a blank screen.
I was always irritated with all the closing parens when working in lisp, but I adored reader macros.
To the point of this intrusion: I read your post last night and this morning I read, The Most Important Idea in Computer Science, http://bc.tech.coop/blog/060224.html, which played such sweet point/counterpoint to your melody.
Reginald: Of course they must do many things to be successful. But the current focus is on how to shift bits back and forth and which syntax is used for such, and not about the meaning of why the computer was invented in the first place. And our programming language abstractions were created to shift those bytes around to the convenience to programmers in order to their jobs easier. Through the years and expansion of the development stack, the two ends of the communication stack are farther apart, and the languages and tools in the middle seems to be less and less about conveying the original ideas.
This discussion is about Ruby, and for me, Ruby doesn't add much to how I shift bits around. The environment isn't that different, the syntax isn't that different, the OO paradigms aren't that different, the community isn't that different ... not different to other alternatives such as PHP, Python, Lua, Perl ... heck, I'll throw in Java, .Net and C++ in there as well. Erlang and Lisp stands out in syntax and the functional completeness, but hey, even XSLT does that.
The actual languages aside, the real crux of the matter lies in how people design APIs. That's where the semantics are, that's where we "interface", so to speak. And seriously, if the APIs were gold, I don't care if Ruby syntax is bronze. The point of trying out new languages to me is to see if they can help me communicate between the computer and humans better. If it doesn't do that, what is their point? Seriously?
The trouble I am having with this discussion is that "readability" is all motherhood and apple pie, but when you get down to business there are many ways to skin a cat. (Sorry for the mixed metaphor, but it illustrates a point all by itself, which I will get to in a moment.)
Thus you get the phenomenon in the Java world where there is a huge debate over closures. Not a debate over how to add closures but whether to add them at all. Each side argues that they are trying to make Java programs readable!!!
One side argues they are adding closures to make it easier to communicate certain ideas. The other argues that closures make programs harder to read and that certain problems should be solved with the ideas that are already in the language.
Within Ruby, what's with metaprogramming? By your argument we can hand wave the namespace conflict issues away (labouring to produce correct programs not our problem), but does writing "acts_as_fubar" make a program more readable? How about "find_people_by_ssn"?
Many Rails aficionados say yes. Many people who dislike Rails scream that it makes programs impossible to understand: they cannot find any of the fubar-ish methods in a class definition, and method_missing magic is all mirrors and prestidigitation.
From this, I conclude that "designing a language to help people communicate with each other" sounds wonderful, and certainly it beats writing Brainfuck, but it is not obvious to me that we will all agree on whether the resulting language does the job or not.
In the end, I believe we need to concentrate on writing programs for people to understand. One might be tempted to say, "Aha! If writing a program for people to understand is important, the language we use must be designed directly with this in mind," but I am not so sure.
For example, if a language is so "productive" (whatever that means) that one person can replace a team of three, then perhaps a small YCombinator-style startup can work with one technical person and one business person.
Is readability still paramount? Obviously, eventually. But in the mean time, if they can build something and sell the company for $20 million, who cares?
Or perhaps productivity works another way. If a programmer is twice as productive, instead of producing twice as much code, perhaps she produces the same amount of code but now spends 50% of her time refactoring the code even after it works to be readable.
Given any reasonable programming language, I would lay my money down that someone who spends 50% of their time rewriting their code for readability will produce more readable code than someone who writes it once in a so-called readable language.
Finally, we return to Ruby. Matz said he designed it with giving Joy to programmers in mind. I will state directly that given two programmers of equal talent, if one enjoys their work and the other does not, the one who enjoys their work will produce better work by any measure you can articulate, including producing readable code.
Again we come down to this: readability is a property of programs, and the influence of a language on the readability of the programs is indirect. That does not mean the language doesn't matter, but it does make me suspicious of the argument that we can look at one language and say it produces readable programs and look at another language and say it does not.
I agree readability is a problematic word/concept and for me it is one of those horizon words, it appears to be there and no matter how hard you try, you never get to it.
You wrote: "Thus you get the phenomenon in the Java world where there is a huge debate over closures. Not a debate over how to add closures but whether to add them at all."
Are you arguing that there should be no discussion about the merits and impact of adding new functionality to something? Or that the discussion of merit should be secondary to the one about how to implement the addition? Or that in the case of Java discussions about readability are an oxymoron?
On the other side of the coin; for me reading is a human activity and try as I might, I could not find the reader in your discussion on readability.
Let me leave you with a quote I like from my friend Mark Baker:
You wrote: "Thus you get the phenomenon in the Java world where there is a huge debate over closures. Not a debate over how to add closures but whether to add them at all."
Are you arguing that there should be no discussion about the merits and impact of adding new functionality to something? Or that the discussion of merit should be secondary to the one about how to implement the addition? Or that in the case of Java discussions about readability are an oxymoron?
No, no, and most definitely no.
I am arguing that while it is easy to agree that languages ought to facilitate writing readable programs, it is not easy to derive any tangible heuristics for language design from this extremely motherhood and apple pie sentiment.
The example of Java folks arguing about whether to include closures suggests to me that we can take an idea this is fifty years old and reasonable, intelligent people can disagree over whether it will facilitate writing readable programs.
And so it goes for almost any language design feature. Pythonistas say that standardizing indentation make programs more readable. Detractors say that there is a trade-off, easily seen in that Python's lambdas are severely restricted because there is no easy way to parse a multi-line lambda. But supporters claim you can do almost anything with a named function. SO you can again have two reasonable, intelligent people who cannot agree about whether significant whitespace makes programs more or less readable.
So my suggestion here is that it's okay to debate language features, it's okay to debate implementations, and it is certainly possible to evolve Java to b more readable.
However, I do not think it is easy or even possible to design a language to produce more readable programs directly. I think that arises from people trying to write more readable programs, and supporting that effort. The distinction may seem subtle, but it is very real.
Reginald: I've tried to give it a better think and a summary over on my blog. Sorry for taking time and writing so much about this, but this is a complex issue for me, one I feel rather strongly about. I don't think we disagree much actually, but perhaps slightly see past each other in the way to solve (in which I'd fight the language with swearing a lot, but you perhaps would accept and move on? :) it.
I sympathize with the desire for cleanliness and elegance found in, say, Scheme.
I have several years experience in Lisp, Scheme, and Ruby. And it is a little known fact that Matz was a lisper at the time he wrote ruby. He even contributed several packages to Emacs.
Here's an analogy. Consider the design of the piano keyboard layout. You might say it is "optimized" for diatonic C, while it's "ugly" or "inconsistent" for the other keys (besides than A minor, heh).
But piano players LIKE the resultant peculiarity --- the odd geography of the scale in non-C keys. The scales have unique characters which are USEFUL and even LOVED.
The keyboard could have been designed in a "simple, elegant" manner by making all the keys uniform, thereby making transpositions trivial. One has to wonder why this "obvious" design didn't take.
Another example is the "ugly" distance of a major 3rd between the G and B strings of a guitar, whereas all the other strings are separated by 4ths.
Yet people love the guitar, along with this quirky major 3rd which screws up the symmetry and "purity" of the instrument. At the very least it's useful because it makes the top and bottom strings the same note, E. It's a trade-off between practicality and the "platonic beauty" of a "mathematically consistent" instrument.
In many years of ruby programming, I've hardly ever used the 'return' keyword, so I've never encountered the problem you mentioned. Though I understand your concern, it's had no practical effects in my case.
Writing the variable or statement without the "return" keyword is sufficient. If you plan to exit from a method prematurely, it is better to declare that you are doing so with try/catch.