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'.

Friday, October 06, 2006

Where does static really fit?

Graham Rocher suggests that closures cannot be added to Java because Collection cannot compatibly grow in number of methods (methods like forEach, any, all, etc.), and apparently adding static methods to the Collections class is not a solution in these halcyon days of dependency injection. He does acknowledge that static methods are sometimes useful, but so far he hasn't said where or why. He says that Math.abs is a poor example, because you never need an alternative implementation. However, StrictMath.abs is an alternative implementation (though not used often). Suppose that you wanted to be able to choose whether to use Math or StrictMath's abs implementation. You could make or use an interface that contains the 'abs' method, and make two implementations of it. I know that many blog readers find generics difficult to understand, and some even find anonymous classes tricky, so I'll be accommodating..


interface Abs
{
    double abs(double value);
}

final class LaxAbs implements Abs
{
    public double abs(double value)
    {
        return Math.abs(value);
    }
}

final class StrictAbs implements Abs
{
    public double abs(double value)
    {
        return StrictMath.abs(value);
    }
}
Graeme's argument is that, because substitutability is required, 'forEach' and friends should not be static, so that their implementation can be provided through dependency injection. It can be seen from the above example that, given static methods, it is possible to make versions that are DI-compatible, so the static implementation can then be ignored (as it is wrapped). Then, why can't the implementation of forEach be static? For the users who just want to use the implementation given in the SDK, Collections.forEach is fine. For those who want to be able to substitute it for others, wrapping it, or using a wrapped version provided by the SDK, is trivial. I'd suggest that you can safely implement any algorithm as static, and, if there is a need, you can provide a non-static way of accessing it.

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.