Friday, July 18, 2008

Java Is Too Academic

Occasionally you'll hear people praising Java because it's incredibly readable; there's only one way to do things; and other languages are too academic. But I have proof that they're wrong. After you view the code below I think you'll agree with me that with Java:

  1. It's far too easy to write code that's illegible to most programmers.
  2. There are far too many ways to do things.
  3. It's far too academic.

I have reason to believe other popular languages like Ruby and Python allow this sort of thing, too. When will we finally stop inventing such academic languages and let real world working programmers get some real world work done?

The proof that Java is too academic and obscure? Right here: factorial using a fixed point operation in an applicative order lambda calculus. "Obscure" and "academic" don't even begin to describe this code*.

public interface Lambda {
    public Object apply(Object x);
}

public class Factorial {
 public static void main(String[] args) {
  final int x = new Integer(args[0]);
  
  System.out.println(x + "! = " + 
  ((Lambda)(new Lambda() {
   public Object apply(final Object f) {
    return new Lambda() {
     public Object apply(final Object x) {
      return ((Lambda)f).apply(new Lambda(){
       public Object apply(final Object y) {
        return ((Lambda)((Lambda)x).apply(x)).apply(y);
       }       
      });
     }     
    }.apply(new Lambda() {
     public Object apply(final Object x) {
      return ((Lambda)f).apply(new Lambda(){
       public Object apply(final Object y) {
        return ((Lambda)((Lambda)x).apply(x)).apply(y);
       }       
      });
     }     
    });    
   }   
  }.apply(new Lambda(){
   public Object apply(final Object f) {
    return new Lambda() {
     public Object apply(final Object x1) {
      final int x = (Integer)x1;
      return x == 0 ? 1 : x * (Integer)((Lambda)f).apply(x -1);      
     }
    };
   }   
  }))).apply(x));
 }
}
* "Obscure" and "academic" may not begin to describe it, but "perverse" starts getting close

16 comments:

J. Suereth said...

<sarcasm>You're just taking your academic nonsense and perverting a good language for real developers. Keep your shenanigans for languages with funny names. I'll stick to my food and/or letter-based languages, thank you very much</sarcasm>

Anonymous said...

You can write bad code in almost any language, thats why you should hire good developers who don't write unmaintainable code.

Your argument is like saying you can drive from L.A. to New York via Mexico therefore the road system is too "academic". A good driver will use a good route, a really poor one will point out the sites in Mexico city.

Erik Engbrecht said...

I hereby sentence you to 8 hours of hard Scala documentation writing for increasing the already overwhelming supply of nonsense code in the world.

Michael Vanier said...

Wow, James, where the hell did you get that code?

To be completely fair, fixed-point combinators (especially applicative-order ones) are really gross in almost any language. Here's how it looks like in Scheme, a language well-suited for writing such things:

;; with explicit recursion
(define Y
(lambda (f)
(lambda (x)
((f (Y f)) x) )))

;; without explicit recursion
(define Y
(lambda (le)
((lambda (f) (f f))
(lambda (f)
(le (lambda (x) ((f f) x)) ))) ))

(define factorial
(Y (lambda (fact)
(lambda (n)
(if (= n 0)
1
(* n (fact (- n 1))))))))

James Iry said...

@j.suereth: you say I'm perverting a language but I say it was asking for it by dressing up in those sexy high level abstractions.

@al sutton: please don't say my code is bad. It hurts my feelings. How would you write an applicative order fixed point combinator in Java? Also, I like going to Mexico.

@erik engbrecht: I'm so ashamed.

@michael vanier: the code is a pretty mechanical translation of λf. (λx. f (λy. x x y)) (λx. f (λy. x x y)) (plus some factorial stuff) - it's just hard to recognize. The only wrinkle involves my choice to do dynamic typing using casts. I can't embed the Y combinator using a typed λ-calculus as "interface Lambda[A,R]{public R apply(A x)}". The purist untyped λ-calculus would, of course, be "interface Lambda {Lambda apply(Lambda x)}" but that would mean Church encoding numbers and building subtraction/multiplication/etc. It's doable but that would be some gnarly Java! Far too gnarly for a little joke. :-)

James Iry said...

In my previous comment I said I couldn't do it with a simple Lambda[A,R] type. But it can be done with recursive types. Somebody beat me to it: A bit of truly ridiculous java geekery

Anonymous said...

If you want improvements try writing code to fit the problem and don't impose artificial restrictions just to proove your own point.

A factorial class is as simple as;

public class Factorial {
public static void main(String[] args) {
int x = new Integer(args[0]);
int total = 1;

while( x != 0 ) {
total *= x;
x--;
}

System.out.println( args[0] + " != "+ total );
}
}

It's readble, maintainable, and if all you need is a class to calculate a factorial why would you want to do anything else?

James Iry said...

@al sutton,

OMG, there's another way to do factorial in Java? Geez, this language is practically like Perl there are so many ways to do things. Since I wrote the originaly post I've also learned that you can write

public class Factorial {
public static void main(String[] args) {
System.out.println( args[0] + " != "+ factorial(new Integer(args[0])) );
}
private int factorial(int x) {
return x == 0 ? 1 else x * factorial(x-1)
}
}

or even

public static void main(String[] args) {
System.out.println( args[0] + " != "+ factorial(new Integer(args[0]), 1) );
}
public int factorial(int x, int accum) {
return x==0? accum : factorial(x-1, accum * x)
}
}

The mind BOGGLES at the shear variety of academic obfuscation this language encourages! That's 4 ways already. Why can't we have a language with only ONE blue collar way to do factorial? In fact, why do we have languages that can even do factorials? That's something we blue collar programmers never actually need to do!

Al, help me!

Anonymous said...

All I'll say is that you can try to put a screw in a piece of wood with a hammer, and some people will want to just to proove that hammers and screws are hard to use, but the rest of us just grab a screw driver, get the job done, and hope those using a hammer find themselves a nice place to work where they're not going to distrurb anyone else.

James Iry said...

@al sutton,

I couldn't agree more. You're right that people will insist on pounding down the recursive factorial screw with a big ol' iterative hammer. Ugly, ugly! Thanks for bringing that possibility to my attention!

But the point of this article is that so many languages let you build hammers where screw drivers would work better. Isn't there a language that will ensure that I never have to deal with code that I don't understand easily?

Ricky Clarkson said...

System.out.println(new Object(){ int fac(int x) { return x<2 ? x : x*fac(x-1); } }.fac(5));.

Michael Vanier said...

@al sutton: Were you aware that "proove" is actually written "prove"? And that "thats" is actually written "that's"? Bad grammar doesn't make your argument any more persuasive.

I think that one of the points that James was making is that although Java may be "readable" for the kind of ho-hum programs that most people write, when you want to write something more sophisticated (such as functions that can accept other functions as arguments or functions that return functions, both of which are trivial to write in functional languages and extremely useful), then Java is a miserable pain in the ass to use.

@James: The orthogonality argument ("there should only be one way to write something") is a bogus goal, since nearly every language provides multiple ways to write nearly everything. However, I do prefer languages that don't go out of their way to provide redundant syntactic ways to write the exact same code. Both Perl and Ruby are guilty of this. Java isn't particularly redundant at a syntactic level; its faults lie elsewhere.

James Iry said...

@michael,

Actually, the whole thing isn't a satire of Java's verbosity - at least not as its central theme. It's really a satire of a mindset that people have that languages like Java are "blue collar" while languages like Scala are "academic." I wanted to show that the distinction is usually pretty arbitrary - academic concepts are expressible even in the most "blue collar" of languages.

It's also a satire of a common expression that in some languages there's only one way to do things. As you point out, that's never true. If a language is Turing complete you can always do things other ways - at the extreme you can write an interpreter for language B in language A and then solve the problem in B's way.

Finally it's a satire of Java's frequently touted readability. It's trivially easy to make code that works but is illegible. In fact, having used languages like Scheme, Haskell, and Scala (and, yes, Ruby) I find much about Java to be an impediment to comprehension. Higher order functions are the tip of the iceberg.

Anyway, as to your comment about syntactic orthogonality I leave you with this - would Haskell be as pleasant to use without "do"? Would you really want a C without both "for" and "while"?

@al sutton,

Thanks for being such a good straight-man. Hope I haven't offended too much. And for the record, in Java I'd use the iterative version.

But I still like going to Mexico.

Anonymous said...

@michael

So if I used a spell-checker you'd see my argument as more persuasive? For me content being more important than perfect grammer and spelling is something I learned long ago.

@james

No worries. I'm just waiting for the first person to report that someone has cut and pasted the code in your original post and submitted it as their homework :)

Alex said...

Hi I know this post is a year old, but when I saw it, I thought there must be a better way to do that program in Java. So here is my attempt. I have merged the lines declaring new objects with those declaring their functions, since the objects are just a means to then end of defining new functions. And I managed to restrict the dynamic casting to the case where a non-Lambda type is used, with my ObjectLambda class.

public interface Lambda {
public Lambda apply(Lambda x);
}

public class ObjectLambda<T> implements Lambda {
private T value;

public ObjectLambda(T value) {
this.value = value;
}

public Lambda apply(Lambda x) {
// reproduce the behaviour of a dynamic typing approach
throw new ClassCastException();
}

public T getValue() {
return value;
}
}

public class Factorial {
public static void main(String[] args) {
final int x = new Integer(args[0]);
System.out.println(x + "! = " + factorial(x));
}

public static int factorial(final int x1) {
final Lambda x = new ObjectLambda<Integer>(x1);
final Lambda rv =
new Lambda() { public Lambda apply(final Lambda f) {
return new Lambda() { public Lambda apply(final Lambda x) {
return f.apply(
new Lambda() { public Lambda apply(final Lambda y) {
return x.apply(
x
).apply(
y
);
}}
);
}}.apply(
new Lambda() { public Lambda apply(final Lambda x) {
return f.apply(
new Lambda() { public Lambda apply(final Lambda y) {
return x.apply(
x
).apply(
y
);
}}
);
}}
);
}}.apply(
new Lambda() { public Lambda apply(final Lambda f) {
return new Lambda() {
public Lambda apply(final Lambda x1) {
final int x = ((ObjectLambda<Integer>) x1).getValue();
return new ObjectLambda<Integer>(x == 0 ? 1 :
x * ((ObjectLambda<Integer>) f.apply(
new ObjectLambda<Integer&gt(x - 1)
)).getValue()
);
}};
}}
).apply(
x
);

return (int) (((ObjectLambda<Integer>) rv).getValue());
}
}

Daniel Gronau said...

CAN I HAZ GENERIC VERSHON PLZ??? *imagine cute kitten pic here*