General musings on programming languages, and Java.

Friday, October 20, 2006

A fairer brevity comparison between Ruby and Java

As a Java programmer, I'm not completely convinced that brevity is always good. I know that I can write some pretty unreadable brief code. ~(~0>>>prefixLength) is a nice little example. It converts a prefix length, e.g., /24, in a network number, into a netmask, e.g., 255.255.255.0 (as an unsigned int). However, for this article, I'll put readability on the backburner, somewhat. When I stumbled across Sometimes Less is More by Peter Szinek, who appears to like being photographed with camels, I found that some of the Java code posted seemed to be written by, well, someone who didn't like Java. In this post I'll try to suggest better Java examples. I will omit imports and method declarations unless they are relevant. 1. Ruby:


10.times { print "ho" }
or
print "ho" * 10
Perl possibly has a more sane syntax, print "ho" x 10; - this way '*' doesn't mean both multiplication and repetition. I'm not too bothered either way on this. The article actually gave no Java equivalent, here's one: out.println(format("%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s%1$s","ho")); out is System.out, and format is String.format, imported statically. Obviously if you make a method to do this, the calling code becomes very very short: out.println(repeat("ho",10)); repeat comes from cirrus.hibernate.helpers.StringHelper (found via Google Code Search). 2. Ruby: if 11.odd? print "Odd!" The article showed this as the Java equivalent:
if ( 1 % 2 == 1 ) System.err.println("Odd!");
However, this only works for positives and zero. I'd prefer:
if (1%2!=0) out.println("Odd!");
(-3%2==-1) Again, with prewritten methods, this can become clearer - there is a prewritten 'odd' method in the JDK 1.4 demos, should this prove hard to write yourself.
if (odd(11)) print("Odd!");  //let's assume print is a method that does System.out.println.
3. Ruby: 102.megabytes + 24.kbytes + 10.bytes Java: 102 * 1024 * 1024 + 24 * 1024 + 10 I'd prefer this a little: 102<<20 + 24<<10 + 10; or with a little predefinition: 102*MB+24*KB+10. I really wonder what the bytes method does in Ruby! 4. Ruby: print "Currently in the #{2.ordinalize} trimester" Java: System.err.println("Currently in the" + Util.ordinalize(2) + "trimester"); My suggested Java: out.printf("Currently in the %s trimester",ordinalize(2)); 5. Ruby: puts "Running time: #{1.hour + 15.minutes + 10.seconds} seconds" Java: System.out.println("Running time: " + (3600 + 15 * 60 + 10) + "seconds"); Suggested Java: out.printf("Running time: %d seconds", 1*HOURS+15*MINUTES+10); 6. Ruby: 20.minutes.ago Java: new Date(new Date().getTime() - 20 * 60 * 1000) Suggested Java: new DateTime().minusMinutes(20) - DateTime is from Joda Time. 7. Ruby: 20.minutes.until("2006-10-9 11:00:00".to_time) Java: Date d1 = new GregorianCalendar(2006,9,6,11,00).getTime();
Date d2 = new Date(d1.getTime() - (20 * 60 * 1000));
Suggested Java: new DateTime(2006,10,9,11,0,0).minusMinutes(20) 8. Ruby:
class Circle
  attr_accessor :center, :radius
end
Java: Too long, see the original article! Suggested Java:
class Circle
{
    public Coordinate center;
    public float radius;
}
"a simple class definition having 10 fields in Java will have 80+ lines of code compared to 1 lines of the same code in Ruby." For a lot of classes, many of those fields will be immutable anyway, or at least not exposed via getters/setters. In the case of an anonymous class, there are zero extra lines of code for them. 9. Ruby:
stuff = []
stuff << "Java", "Ruby", "Python" #add some elements
Suggested Java:
List<String> stuff=arrayList();
stuff.addAll(asList("Java","Ruby","Python"));
10. Ruby: stuff = [”Java”, “Ruby”, “Python”] Suggested Java: List<String> stuff=asList("Java","Ruby","Python"); 11. The author complains that you have to sort arrays using Arrays.sort(array) instead of array.sort() - however, this can become sort(array) via a static import. 12. I think the stuff about stacks and arrays would be solved by using java.util.Stack, which has pop/push/subList etc. I don't see a great need for the static implementation to appear as part of the object though. I prefer thin objects. 13. The author seems to think that adding 'nil' values to an array/list when you try to add to an index beyond the end of the array/list is a good thing. I rather like the little protection that Java gives in that if you want null values you have to add them yourself (except with the new String[10] syntax). 14. Ruby: File.read('test.txt').scan(/.*?\. /).each { |s| puts s if s =~ /Ruby/ } Suggested Java:
import static java.lang.System.out;
import java.io.*;

class Test {
        public static void main(String[] args) throws IOException
        {
                File file=new File("filename");
                byte[] bytes=new byte[(int)file.length()];
                DataInputStream input=new DataInputStream(new FileInputStream(file));
                input.readFully(bytes);
                String[] sentences=new String(bytes,"ASCII").split("\n");

                for (String sentence: sentences)
                        if (sentence.indexOf("Ruby")!=-1)
                                out.println(sentence);
                input.close();
        }
}
I expect that I could mimic the Ruby way, given time and inclination. The end result would be this, with supporting methods: File.read("test.txt").scan("\n").each(ifMatches("Ruby",print)); 15. Ruby:

          tree = a {
            b { d e }
            c { f g h }
          }
Suggested Java:

Tree tree=tree("a",
    tree("b",leaves("d","e")),
    tree("c",leaves("f","g","h"))
  );
And just as a general comment, I'd gravitate closer to Haskell, with its type inference and very powerful static type system, than towards Ruby, Python et al, because I like the freedom to be able to mess around with my code, knowing that there is an automatic eye-over-my-shoulder that's going to tell me when I do something stupid. This post is not to invalidate the original article; it's clear that the Ruby way of doing things is obviously much simpler in many cases. However, often the idiomatic Java way is not the best anyway. Personally I've been experimenting with functional programming from within Java (is this like asking your wife to dress up as someone else?), and I think closures could really help in any future 'brevity wars'.

16 comments:

Derek said...

C# is making some strides in the "brevity wars" (who declared this war?) as well. I posted a similar follow-up demonstrating its new features. I'm also in the camp which appreciates the "automatic eye-over-my-shoulder" :)

Anonymous said...

If you wish to do functional (or at least functional-ish) programming in a JVM world, I would strongly suggest Scala.

Anonymous said...

For example 1, have it print "hello" every other time and see if you can keep it on one line. Better yet, let's print it 3000 times, not 10. The original article's point is "the power of ruby." Format isn't looping, so you'll have to excuse my lack of awe at Java's power when the substitute for a loop is repeating a peice of text n times.

From your comments: "let's assume print is a method that does System.out.println"
Shouldn't this say "I'm making assumptions here to let me avoid typing all of the required code, as it ruins my point about Java being concise?

Also, what is this "with supporting methods" hand-waving? I could write a search engine to rival google "with supporting methods." Watch:

printHTMLPage(doSearch("fooo"));

There's my search engine "with supporting methods."

-Eric

Isaac Gouy said...

Personally I've been experimenting with functional programming from within Java...

Instead I suggest you experiment with other languages that target the JVM, such as Scala and Nice

At present, Scala has better documentation and support, while Nice provides an opportunity to try-out multimethods.

There are examples in both languages on The Computer Language Shootout

Anonymous said...

Hi,

I dont mean to cause any flame war. But, I disagree on a few points when comparing java programing to ruby programing.

In Example 1 I do not think that your pure Java solution is equivalent. It outs the same value, but uses a format string ... where in, Ruby need not need make use of that (Ruby has format strings and printf).

In general if you are comparing languages then compare their core copmonents .... any programming task is easier when you can include XXX external non-official 3rd party library. A fine example how a library include can make code smaller is here: http://redhanded.hobix.com/bits/aFileSharingParagraph.html

Lastly, examples 9 and 10 are also not semantically the same. Ruby's array can hold anything, your list is statically typed to contain strings. So whereas Ruby can "#add more elements" of any type ... Java cannot, it can only "#add more elements of type string".

Anonymous said...

A static type system cannot tell you if you are going to do something stupid since it can't catch most semantic errors. For instance a static type system can't distinguish x = 1 from x = 2 so it therefore can't tell you if you made a mistake when you typed the latter while really intending the former.

I think your post shows why we should prefer objects which are thick instead of thin. Your solutions require a lot of static imports and searching in libraries whereas with Ruby a lot of the behaviour you want is attached to the object so you don't have to search for it.

14) I think is completely wrong unless I am missing something. What exactly does the IfMatches method return? Also in the Ruby example it is possible to change the code to anything you want not just an if statement.

Anonymous said...

haha "..with supporting methods", "...with a little predefinition".

cracks me up.

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...

Well written. Too bad hysteria rules. TV's fault I guess.

Anonymous said...

Drop the Groovy-all.jar in your classpath and you should be able to match line for line every one of the Ruby examples. (You'll also need to use a different compile command, "groovyc").

Ricky Clarkson said...

"Better yet, let's print it 3000 times, not 10"

Sure, but I was looking at character counts. For 10 times, the way I showed was the shortest in numbers of characters. If the original post had been 3000.times("hello") then my answer would have been different.

For example, you could write a times(int,String) and then call it with times(3000,"hello").

"Also, what is this "with supporting methods" hand-waving?"

The original blog did not use 'pure' Ruby, it depended on external libraries, so I don't see this as unreasonable.

Ricky Clarkson said...

"A static type system cannot tell you if you are going to do something stupid since it can't catch most semantic errors. For instance a static type system can't distinguish x = 1 from x = 2"

Technically, that's untrue.

Dependent Types let you constrain types by the values they can hold, if I understand them correctly.

Ada lets you declare a type that represents a range of integers, for example. I don't know whether that's checked at compile-time, runtime or both though.

"I think your post shows why we should prefer objects which are thick instead of thin."

Interesting. My reason for preferring thin objects is that their implementation is simpler, and hence less error-prone, as far as I can tell.

Thick objects seem more convenient, but, in Java at least, harder to write well. The mixin features of Ruby, etc., might make a thick object more attractive.

I'd rather keep the things that need to be polymorphic in an object and the things that don't vary in a separate place, as operations ON an object rather than operations BY an object. At least in Java.

Ricky Clarkson said...

"14) I think is completely wrong unless I am missing something. What exactly does the IfMatches method return? Also in the Ruby example it is possible to change the code to anything you want not just an if statement."

This was the line of code the poster referred to:

File.read("test.txt").scan("\n")
.each(ifMatches("Ruby",print));

And to answer the question:

SideEffect<String> ifMatches(String match,SideEffect<String> todo)

That is, ifMatches would take something to run on each string, and run it on the strings that match the string given as the first parameter.

This isn't a real API I plan on writing, just a speculation. Although SideEffect is something I use in my code.

Robbie Muffin said...

I really enjoyed http://www-128.ibm.com/developerworks/java/library/j-ruby/ -- this shows a great comparison with java's meat-and-potatoes, rather than looking at basically one line/functional code samples.

Thomas Mueller said...

new String(new char[10]).replaceAll("\0","ho");

Anonymous said...

what a joke!

This made up class and functions to help support your code will all be rather long.

Most of those active support methods are very small, yet do a lot.

There is a reason there is no Java on Rails. Java is not nearly flexible enough do to what rails does.

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.