|
![](/i/fill.gif) |
I realise this is a radical and controversial thing to say, but it is. :-P
Suppose, for example, that I wanted to implement a lambda expression
interpreter. For those who don't know, a lambda expression has one of
three possible forms:
1. A variable name.
2. A lambda function. This consists of a lambda, followed by a
variable name, followed by an arrow, followed by any valid lambda
expression.
3. A function application. This consists of any two valid lambda
functions, separated by an empty space.
Usually brackets are used to disambiguate when such expressions are
written as text. But conceptually, the above exactly describes the form
of a lambda expression.
Now, suppose we want to define a data structure for storing such
expressions, and a way to convert one into text. (Converting /from/ text
is obviously much harder.)
In Haskell:
data Expression =
Variable String |
Lambda String Expression |
Apply Expression Expression
deriving Show
This defines our data structure. The final line tells the compiler to
auto-generate the code for conversion to text. However, if you don't
like the way the compiler's auto-generated code does it (and we don't,
in this case), it's not hard to write it yourself:
lambda_text :: Expression -> String
lambda_text e =
case e of
Variable n -> n
Lambda n e -> "λ " ++ n ++ " → " + lambda_text e
Apply e f -> lambda_text e ++ " " ++ lambda_text f
In reality, you /probably/ want to make it insert some brackets and
stuff, but the above is the gist of it. If we remove the "deriving"
line, we have a total of 10 lines of code here.
Now let's try the same thing in Java:
public abstract class Expression {}
public class Variable extends Expression
{
private String Name;
public Variable(String n) {this.Name = n;}
public String toString() {return this.Name;}
}
public class Lambda extends Expression
{
private String Variable;
private Expression Body;
public Lambda(String v, Expression b)
{
this.Variable = v;
this.Body = b;
}
public String toString()
{
return "λ " + this.Variable.toString() + " → " +
this.Body.toString();
}
}
public class Apply extends Expression
{
private Expression LHS, RHS;
public Apply(Expression l, Expression r)
{
this.LHS = l;
this.RHS = r;
}
public String toString()
{
return this.LHS.toString() + " " + this.RHS.toString();
}
}
If your screen is anything like mine, that lot doesn't even fit on one
page. This is /a lot/ of code. And yet it doesn't even "do" anything, as
such. It just lets you construct data structures, and print them out.
The same thing as the 10 lines of Haskell code did. I count 44 lines.
Or, if you're feeling generous, 36 non-blank lines. And I even tried to
scrunch Lambda.toString() onto one line, even though it's really too big.
Now suppose that, for some strange reason, I want to be able to check
whether two expressions are the same. (Realistically, I can't think why
you'd want to do that for anything except variables.) In Haskell, I can
simply append "deriving Eq", and the compiler will write the code for me
in the obvious way. In this instance, it does the right thing. In
Java... well OK, I'll go easy on Java and only write the code for
Variable. Here goes:
public class Variable extends Expression
{
private String Name;
public Variable(String n) {this.Name = n;}
public String toString() {return this.Name;}
public boolean equals(Object obj)
{
if (obj == null) return false;
if (obj instanceof Variable)
{
Variable v = (Variable) obj;
return this.Name.equals(v.Name);
}
return false;
}
public int hashCode() {return this.Name.hashCode() + 9;}
}
OK, so only 14 lines of actually new code. The hashCode() override is
not /strictly/ necessary, but the IDE whines like hell if you don't, and
it's highly likely that you're going to put variables as keys into a
HashMap at some point, in which case hashCode() had better work correctly!
In Haskell, you can simply say
e `equals` f =
case (e, f) of
(Variable x, Variable y) -> x == y
and you're done.
In Java, you have to check whether the argument is null. (Impossible in
Haskell.) You have to check whether the argument is of a comparable
class. (Haskell's static type checking does that for you at
compile-time. Actually Java's generics could do that too if they
extended it a teeny weeny bit... but they haven't.) Then you have to
actually type-cast it. (Unnecessary in Haskell due to static checking.)
Then, and only then, can you do the actual test. But because it's Java
and it sucks, you can't just say this.Name == v.Name; that would do a
pointer comparison, not a string comparison. So you must instead do
this.Name.equals(v.name), which is obviously far more readable. :-P
On the plus side, the IDE will generate some of this code for you. On
the minus side, you still have to /read/ it. And perhaps the worst thing
about the above Java code isn't that it's 44 lines, but that it's in 4
separate files, when /obviously/ it's notionally a single entity. (Then
again, I think if you make the classes private - excuse me, "package
scope" - then they may all be put in one file.)
In consequence to the above, I just spent an entire morning coding in
Java, and all I actually have to show for it is a boat-load of classes
which can hold data and generate a graphical representation of
themselves. They have no actual /behaviour/ yet. It's taken me this long
just to do that... Man, Java is a blunt knife! (But oh so hard to hurt
yourself with... yeah, right!)
Post a reply to this message
|
![](/i/fill.gif) |