> This is a perfectly cromulent article, but as someone relatively new to Ruby, I don't see the beauty. Perhaps I've spent too much time with the airless asceticism of, say, Scheme
Yes. Ruby is beautiful when you come from PHP or Java or C++. If you come from Python it's meh because it's a very similar language (though the community is different and a number of recipes/best practices are very different) at the syntactic and at the semantic levels, and if you come from Lisps, from Smalltalk, from ML or Haskell, etc… then it's not "meh" it's "ugh", because the language is far more complex less orthogonal and far more full of weird corner-cases than what you come from.
TFAA came to Ruby from Java, so Ruby was like seeing the Sistine Chapel for the first time.
Instead of a simply giving your subjective aesthetic evaluation, based on some measure of "elegance", you could objectively compare, in HCI terms, the usability of the languages as user-interfaces to the computer. A mouse may be more elegant than a keyboard, but for, say, word-processing, it would have comparatively-horrible usability.
Likewise (and this is just a throwaway argument, not the general point I'm trying to make), Lisp may be more elegant conceptually than Ruby, but when trying to find an individual incorrect line of code to make a change, Lisp's "elegant" uniformity actually hinders scanning, whereas Ruby's bumpy, "inelegant" syntax gives your eyes regularized shapes to abstract sections of code into, and thus visually skip them without having to read into their contents.
However, as the mouse's elegance matches well with a paint program, Lisp matches well with Turing-complete data-specifications ("code as data.") Usability is not a one-dimensional spectrum, like your partial ordering of "beauty" above, but rather a consideration made separately for each task an interface is used for.
Actually, despite all the fanfare about LISP, Haskell, Scheme et al, more useful programs have been written and deployed in, say, Python, than all those combined.
What's the last useful LISP program you have used? What proportion does it consist among the other useful programs you use?
I use Scheme/Haskell for most of my scripting purposes, the only exceptions being the cases where I want to make use of some extant library, in which case I switch to Python/Ruby. I used stumpwm for a while, which was written in Common Lisp, and a few months ago switched to XMonad, which is all Haskell. All my editing is done in Emacs, which uses a variety of Lisp (elisp.)
Given that close to 100% of my time is spent in XMonad, and >50% of my time is spent in Emacs, and every session involves at least some interaction with my collection of scripts... I'd say I use quite a lot of useful programs written in those fanfare-laden languages.
I'm a Python programmer, and I find code written in Ruby to be more beautiful.
It's just a personal preference, but I find anonymous code blocks to be a lot more elegant and intuitive than decorators + with statements + for comprehensions. I also like that classes are open, and that you can execute code inside the declaration of a class and that self is implicit ... making tasks suited for the __metaclass__ concept in Python trivial.
I also find Lisp / Haskell and ML to be really ugly ... as in, I could never look over a piece of code and understand what it is doing without learning the language first (with the learning curve being as steep as it gets).
the language is far more complex less orthogonal and far
more full of weird corner-cases than what you come from
But that's the price you pay for convenience.
E.g. I don't appreciate that ML doesn't have a simple concept like operator overloading, or that in LISP every DSL still looks like LISP. I don't like that a Smalltalk application is not portable between implementations or that applications are big, monolithic images of the virtual machine with everything included, even the IDE.
It's easy to get into these mine-is-bigger-than-yours contests, especially with languages, but you know what works better than formulating half-baked opinions?
Results. It's a lot better to shut up and show me some code ;)
1. You can not really juge the source code of a language if you don't the language. Reading alot of functional code is almost always easier then OO code because of pure functions. Look at this video for example at the videos witch can be found here http://www.janestcapital.com/technology/ocaml.php
2. The learning curve of those languages is not bigger then the learning curve of other languages. Time time I spend with making pretty OO designs in school was way bigger then the time it took me to learn clojure and FP in general but this of course is a issue we can not solve. I just don't like it if people say something as if it is a fact.
3. Is it more convenient to do this
array.select &:even?
better then
(filter even? array)
The later seams more intuitive to me and I didn't have to learn more syntax.
The Homecomputer on witch most programmer learned where to slow for functional languages of that time. So FP could only be used in specialized domains.
Most programmers went form basic (lack of alternative) to C then C++ and now Java thats a path of simularity. Most where never exposed to functional programming and now the all have a hard time to switching there OO brain off.
The only proof we have of something being easier to learn, beside anecdotal evidence ("oh, I tried it and it was easy") is statistical empirical evidence, i.e people actually learning it. Well, people are not actually learning those languages. They are learning the "other ones".
(Where by people, I mean: a significant percentage of people, > 1%. I'm sure me, you and that "professional programmer who gives a shit about Haskell" do not a significant percentage make).
Even in universities, students find functional languages difficult, and opt-out of those classes.
> I also find Lisp / Haskell and ML to be really ugly
Wow.
> I don't appreciate that ML doesn't have a simple concept like operator overloading
ML operators are nothing more than functions. The issue of "operator overloading" comes from the type system and the simplicity of the language.
> or that in LISP every DSL still looks like LISP
You might want to read on Scheme reader macros.
> I don't like that a Smalltalk application is not portable between implementations
It's true that you don't have this issue in Ruby as the "standard" is simply the original implementation (aka there is no standard at all, and not really any competing implementation).
> or that applications are big, monolithic images of the virtual machine with everything included, even the IDE.
That definitely isn't the case. That's not the case in GNU Smalltalk, and most commercial smalltalk let you create a stripped production image which contains only the necessary code.
> It's true that you don't have this issue in Ruby as the "standard" is simply the original implementation (aka there is no standard at all, and not really any competing implementation).
The C# compiler in .NET also has incompatibilities with the ECMA standard. Need I mention C++, the most widely used and at the same time poorly implemented language ISO standard ever?
In fact, can you mention a single language with a standard that doesn't brake compatibility with its standard?
And yet the point is moot, because compatibility between implementations is manageable for popular languages like Javascript, C++ and yes, Ruby (JRuby, Rubinius). That's not the case for Smaltalk.
<bait>When your language is already at the top of the beauty chart, why would you write a DSL that is not also beautiful?</bait>
It is really a programmer choice. Lisp programmers generally love the way Lisp looks because of the advantages awarded by those looks (check Myth #7 here: http://www.algo.be/cl/myths.htm). But you can certainly create a DSL that requires more extensive string parsing, and CL provides a lot of help towards that end, including native translations of strings to Lisp objects where your parser needs them.
while we're complaining - my big problem with, really, programming languages in general, these days, is that they're getting good at making it easy to do complicated things within a process but unfortunately typical development projects are getting to be more distributed, which means that the nice abstractions your languages provide usually can't extend across your entire application, so you end up dumbing things down, relying on stuff like web frameworks which imo tend to be a bunch of hacks, etc. (This is probably the reason for the current popularity of dynamic/untyped languages - why bother specifying types when the invariants they create will only hold across part of your application anyway?)
I'd like to see more effort put toward creating programming environments that provide clean abstractions that span entire multi-process distributed applications.
Ruby is nicer to read than Python. Here's how each language reverses a string:
# Ruby
string.reverse()
# Python
string[::-1]
Which looks better?
Edit: I'm not sure why this is being down voted. It's a true statement.
Edit2: With all the down voting on Hacker News recently, I'm about to quit sharing my knowledge here. Maybe pg will do something about this. It needs to change otherwise good people will stop posting here.
I think possibly because it's 'fiddling around the edges'. Sure, it's nice to have a way to reverse a string, but you don't do that very often in practice - assigning to lists, calling functions, etc. are all far more common.
print " with no line breaks."
printf("\n\nprintf ... %7.2f,... %s.",3.14156,"me")
Eugh brackets are optional for invoking functions? So I can no longer just look for () after a name to determine what is a function invocation?
def welcome(name)
puts "howdy #{name}"
end
"end"? Seriously? Verboseness... (This is one of the things that for me, makes ruby very ugly. It's needless verbosity and doesn't have any advantage over using {} ).
It cannot please everyone so if you are happy with a less "conversational" language, stick with it. "Unless", for example, is what I think to myself so "if not" is a suboptimal translation thereof.
I would point out, however, that printf is fairly rarely used and that I am not familiar with fincher.org in the context of sites to recommend newbs to learn Ruby from.
Removing the difference between accessing an attribute (variable) and invoking a method is a solid OO principle. You, as a client of the API, should not care how obj.foo is implemented.
> "Removing the difference between accessing an attribute (variable) and invoking a method is a solid OO principle. You, as a client of the API, should not care how obj.foo is implemented."
I disagree. Code execution is not data access. They are very separate concepts.
Inasmuch this conversation is subjective, I just do not think that statement is objectively true. It is a basic OO principle that the outside world is not privy to the inner workings of an object (or class if you prefer).
Whether you like that or not is immaterial. You are free to avoid that aspect of OOP or the paradigm altogether.
It's called the Uniform Access Principle, as I've already mentioned in a separate comment. Getting back to your example, it is not very useful without mentioning a language under examination. There is no single definition of OOP, and every language implements in a slightly different way.
There are languages where UAP is enforced by the syntax (eg. Eiffel, Smalltalk, Ruby) and languages where it's a matter of convention. But even in the latter it's considered a good practice to keep properties private and encapsulate them with accessors. Some even have dedicated features that help with that (auto-properties in C#, properties in Python).
> I haven't studied OOP extensively in a strict setting, but I would have said that in the OO world, a property is completely separate to a method.
There is absolutely no reason for that, and the grandparent of all things object-oriented (Smalltalk) very strongly disagrees with you: everything is a message, and there are no properties.
> A property defines data about an object, and a method defines something you can 'do' with the object.
A getter method is a property and a method. Properties are irrelevant, access should be uniform, the implementation details of an object (whether a "property" provides direct access to internal data or not) is of no concern to the user of the object.
A property is nothing more than a method whose contract is "give me some data"
There's OOP qua Smalltalk/Kay, and then there's OOP qua Java. You're describing OOP qua Java, which is an ugly type of OOP designed for certain performance advantages.
A Java developer complaining about Ruby's supposed verbosity? What's the world coming to?! When one looks at Java code, 90% of it is just syntactic sugar, which doesn't add much to the actual working code. One has to filter through large amounts of white noise to get to the crux of the code. Not so with Ruby (and many other languages).
This is exactly what the original article was saying. ;) The languages take very different mindsets though, so if someone is actually happy with Java, then it's very possible that they don't have (or want) the right mindset for using Ruby.
"public static final" is not particularly verbose. It's a one time upfront cost which defines a few flags. It's error free, since a typo in any of those keywords will be picked up.
It can also be automated easily etc.
But Ruby using 'end' etc instead of curly braces, that's going to add up to a lot of verbosity.
With regards to differentiating methods, that's simple. "obj.name" calls a method. An object's variables are hidden inside the object, so the only way to access them is via methods anyways.
Furthermore, variables can't be called. If you have a proc object in a variable, you use "myproc.call" to invoke it. The only ambiguity is between methods call within the same 'self' and local variables:
def foo
42
end
def bar
puts foo #=> 42
foo = 30
puts foo #=> 30
puts self.foo #=> 42
end
That's because methods effectively -are- properties. See:
class Foo
attr_reader :bar
def initialize (num)
@bar = num
end
end
puts Foo.new(42).bar
attr_reader is a class-level method that defines a getter method for the named variable. There's also attr_writer, for setting, and attr_accessor, for both at once.
Here it is, converted to java. You tell me which is more verbose.
public class Foo
int bar;
public Foo(int bar) {
this.bar = bar;
}
public int getBar() {
return bar;
}
public static void main(String[] args) {
System.out.println(new Foo(42).getBar());
}
}
You should make bar private, and add a setter. After all, we might want to change the rules on setting bar sometime, and you wouldn't want to have to go back and change everywhere that accesses it...
It's package-access by default, that's enough to hide it from the third-party users of the class.
> and add a setter
The original code uses `attr_reader` which defines a readonly "property". The Java translation is a single getter, with no way to set the value from outside the class.
1. bar's visibility is package local, so your code is already broken, bar is invisible outside its package
2. let's say you solve this conundrum by making the field public
2.1. This piece of code is now frozen solid, you will not be able to ever refactor it (where refactoring is defined as changing implementation without changing behavior) since you can not transparently swap fields and methods in Java
2.2. This piece of code is dead to abstract types, as fields can not be part of interfaces in Java.
2.3. This piece of code is incompatible with Java Beans and will therefore be unusable in e.g. JSP taglibs
JSP taglibs? wtf are you talking about. I'm in no way defending the idiocy of 'enterprise java'.
and your other points don't really make any sense at all. There is no such thing as 'fozen solid code'. Unless you're interfacing to external libraries etc and have agreed on a specific API, which is unlikely to include "yeah just reference 'bar' directly" unless you're insane.
until java has a var keyword and closures, there is no way you can claim that it's less verbose than languages like ruby or python.
I can't count the amount of time I wasted typing things like
LongVariableName longVariableName = new LongVariableNameImpl();
as a java programmer. Also the amount of extra typing for creating anonymous inner classes made GUI coding a real chore. Sure your IDE can automate lots of it, but emacs automatically types end for me when I type def so it's not even a consideration.
Your comment comes across as someone who is either trolling or has no real experience in a language outside of java.
1) If you're reading bad code, that could be a problem. But 99% of Ruby code I read has been written by people who know when they need parentheses/brackets to make the code readable (e.g. when it's not the only code on the line.) But there are tons of times when it's wonderful to be able to leave out the parentheses.
-
2) It might be because I spend a lot of time with Ruby, but I much prefer `end`s to curly brackets, they're just easier on the eyes, especially when they're going to take up the entire line anyway:
a
b
c
d
end
end
end
end
a {
b {
c {
d {
}
}
}
}
Of course, at times I wish Ruby used indentation to declare structure, like Python.
-
3) I don't think I've ever had that problem. If I'm calling `person.full_name`, I don't care if that's a variable, or a method joining first_name and last_name. And if I it was a method, ideally I'd have a corresponding `full_name=(str)` method defined too, so `person.full_name = "Joe Bloggs"` would work, and split the string into the first, last, and optional middle names.
-
4) Why? Why not? What makes this easier to read?
puts "Bankrupt" if balance <= 0.0
Ruby is a lot like the English language; when you're learning it as a second language, you're constantly confused and frustrated by it's weird syntax, but once you get past those initial hurdles, it just clicks. I love Ruby because I never feel like I'm programming in it, it almost feels like pseudo code that's not going to actually execute.
Essentially you could get any non-programmer to solve a problem on paper, and turn it into Ruby code which, without too much fuss, describes their process.
# get the customers text file
# each customer is on their own line, w/ their name and age separated by a comma
# group the customers by age
# show me the top 3 most common ages
file = File.open('customers.txt')
customers = []
file.each_line do |line|
name, age = line.split(",")
customers << {:name => name, :age => age.to_i}
end
grouped_ages = customers \
.group_by{ |c| c[:age] } \
.map{ |c| [c[0], c[1].size] } \
.sort_by(&:last) \
.reverse
grouped_ages.first(3).each do |age, size|
puts "#{age} years old: #{size} customers"
end
Four blocks of reasonably clean code (except the 3rd, that could be cleaned up a bit), which implement the four lines of plain english instructions.
>I love Ruby because I never feel like I'm programming in it, it almost feels like pseudo code that's not going to actually execute.
Uh, you realise that describes any high level language, right? Ruby scores pretty low on the readability scale, about halfway between Perl and Python, but I find it's more familiarity with a particular language that determines how readable people find things.
Here's a quick Python version. The main thing to notice is that while the overall flow/structure is similar, I don't have a lot of { } << \ characters hanging around, and I don't have to chain blocks together since I have list comprehensions.
from collections import defaultdict
customer_file = (line.strip().split(',') for line in file('customers.txt'))
grouped_ages = defaultdict(list)
for name, age in customer_file:
grouped_ages[age].append(name)
age_lengths = sorted( [(len(names), age) for age, names in grouped_ages.items()], reverse=True )
for length, age in age_lengths[:3]:
print "%s years old: %s customers" % (age, length)
Looks kinda nasty to me, but then I'm not too familiar with Clojure/Lisp/whatever-the-hell-that-is ;)
I think one of the side effects of learning a Lisp is that you get a bit blasé about using map. Do you find list comprehensions easier than map/filter? Is there a list comprehension equivalent in Lisp, or would you have to write your own?
As for preference, given that map, filter, etc., are lazy, I like the clear visibility of each step working on each element of the sequence.
Clojure has a list comprehension, for, though using it here is more trouble than it's worth since you have to group and sort, both of which require you to step outside the comprehension and work on the whole list; that's why your code has multiple sections.
Instead, I see the example as just a selection/transformation process on sequences of data. Each step in the clojure code takes and returns a sequence of values (the ->> macro weaves them together):
0. a sequence of lines from a file
1. a sequence of age strings
2. a sequence of [age, [ages...]] tuples
3. a sequence of [age, count] tuples
4. a sequence of [age, count] tuples sorted by count desc
5. a sequence of age strings
6. a sequence of up to 3 age strings
Fun detail: The with-open macro is akin to python's with statement, supports arbitrary number of items, is closed in reverse order of their appearance, and didn't require a change to the language to implement ;)
I wanted to reply to anthonyb's comment about macro use changing the language in the clojure example but couldn't, I guess because it's too deeply nested.
Anyway, ataggart didn't write a macro he just used one that's already part of the language. Using a macro does not count as changing the language.
He was talking about with-open not needing a change to the language to implement. In other words, Python's with statement did need a change to the language, but then Python is a (semi-)interpreted language written in C, so it's a cheap shot - just what I'd expect from a dirty Lisp hacker ;)
Well, you need to sort by occurrences, so you have to work with the whole list in this particular case. I also don't think that having two sections is a particularly big deal, since semantically you're working with two separate things too (the list of people+ages, and the aggregated list of names+a count). If you wanted to do something else too (say, list the names as well) then having an intermediate form can come in handy.
Also, I'm pretty sure that writing a macro counts as 'changing the language', although that line's a bit blurred when you're talking about lisps ;) ;)
Note, I wasn't criticizing the python implementation. It's a perfectly reasonable way to model the problem.
You brought up list comprehensions, and something about being "blasé about using map". I wanted to show how a different way of modeling the problem yields a different structure to the code (namely, as a chain of transformations on sequences).
One is not better or worse than another. One of the things I enjoyed most about learning clojure was forcing myself to think in these different ways.
Well, I was comparing it to something like Java. Though Python still scores far lower on my readability scale. It's seems like it's halfway to being minified, the lines are chock-a-block.
Also, this is a better version in Ruby:
file = File.open('customers.txt')
customers = {}
file.each_line do |line|
name, age = line.split(",")
customers[age.to_i] ||= []
customers[age.to_i] << name
end
grouped = customers.sort_by{|age, names| names.count}.reverse
grouped.first(3).each do |age, customers|
puts "#{age} years old: #{customers.count} customers"
end
…not sure what I was thinking before, it was late.
> Python still scores far lower on my readability scale. It's seems like it's halfway to being minified, the lines are chock-a-block.
That's likely because you're not used to reading Python. For me, Ruby seems unreadable - you have all these weird << and ||= assignments, {||} blocks, etc. I don't parse those as well, because I don't program much Ruby. I'm sure if I did, it'd make a lot more 'intuitive' sense - ditto for you and Python's list comprehensions.
<< and >> are rudimentary it's almost always exactly the same as unix dated from 1970's redirection. e.g: $ echo 'crap' >> somefile.txt. ||= reads as 'or equal', simple.
Yeah, I know what they do - eg. ||= being a Ruby idiom for 'set if this currently evaluates as false'. The point is that because I don't often program in Ruby, it's less readable (for me). Similarly, if you're not used to list comprehensions they'll seem opaque - but I use them all the time, and find them far more readable than equivalent expressions using map/filter.
That said, I do find the more "squiggly" languages (Perl, Ruby, C) to be less readable than the equivalent Scheme or Python, and I suspect that it's because there are fewer weird characters, so it's closer to English.
The groupby, if done using itertools, will cut some code.
from itertools import groupby
customer_data = (line.strip().split(',') for line in file('customer.txt'))
grouped_ages = groupby(customer_data, key=itemgetter(1))
print sorted((len(list(g)), k) for k, g in grouped_ages)[-3:]
A meta-question: why do people keep downvoting this comment? It's been up and down ever since I posted it. Is it that I'm critical of Ruby? I would've thought that if I take the time to write some code and respond in a rational way, then it should be worth at least a couple of karma. What gives?
Trying to make a programming language modeled on plain english instructions is a recipe for disaster. Perhaps that's why I find your example at the end pretty ugly code.
There's ways to make anything look hideous, as for your points.
* end vs {} is one more character, I can't believe I even typed this as it's so stupidly pedantic.
* Keywords.
I have a strong dislike for the now-nearly-meaningless terms "beautiful", "eloquent", "elegant" &c. but for me the deciding factor between Ruby and Python for a project (assuming no constraints from libraries required and such) is Ruby's internal consistency. Python is a bit more haphazard; the infamous `len(str)`, to wit.
> Python is a bit more haphazard; the infamous `len(str)`, to wit.
There is nothing infamous about it. It's a multimethod, the designers of Python decided that mandating the same function for everything (via a common protocol under the covers) was better than risking people implementing a thousand different method names. They did the same with the iterator protocol.
I think that is a poor argument since the same is not true of every function. Therefore, it feels inconsistent to me, which is the only basis I can make judgments on.
I think the GP was referring to "mandating the same function for everything (via a common protocol under the covers) was better than risking people implementing a thousand different method names".
I wouldn't call Python haphazard, it's just a different approach. Ruby was designed from the ground up as an object-oriented language, and the fact that everything is a method call (message) gives it a touch of consistency. This is different from Python which is a multi-paradigm language. In fact the early releases didn't support objects-oriented programming at all. On the other hand Python may feel more consistent because of the fact that, in contrast to Ruby, there is one obvious way to do something[1].
Yes. Ruby is beautiful when you come from PHP or Java or C++. If you come from Python it's meh because it's a very similar language (though the community is different and a number of recipes/best practices are very different) at the syntactic and at the semantic levels, and if you come from Lisps, from Smalltalk, from ML or Haskell, etc… then it's not "meh" it's "ugh", because the language is far more complex less orthogonal and far more full of weird corner-cases than what you come from.
TFAA came to Ruby from Java, so Ruby was like seeing the Sistine Chapel for the first time.