General musings on programming languages, and Java.

Friday, June 29, 2007

On how the lack of macros damages Java code

Update - commenters pointed out an obvious mistake - I should have kept the sample code here the same as my real code. I've updated the code samples. Some time ago I asked why unit tests use assertions instead of returning a boolean. I didn't get any particularly convincing answers, so I continued returning a boolean, in my home-grown test framework, that's so small it hardly deserves the name 'framework'. One obvious benefit from using exceptions is that you get the name of the test method and class in the exception output. In my framework, I had to explicitly supply that. Each test object had a getName(). return "TestUninstallingDrivers". Ok, now I have duplication:


public static final UnitTest testUninstallingDrivers=
    new UnitTest()
{
    public boolean test()
    {
        stuff..
        return !card.hasDrivers();
    }

    public String getName()
    {
        return "testUninstallingDrivers";
    }
};
The usual "Java programmer" approach would be to just go back to exceptions, then you'd be able to get your IDE to show you the test method by clicking on the stack trace. However, there were some reasons not to go back to exceptions. I'd instead like to generate the getName() implementation based on the field name. I believe at least 2 out of the 3 major IDEs can help me with some kind of template like this:

public static final UnitTest $x=new UnitTest()
{
    public boolean test()
    {
        //Auto-generated method stub.  To stop this
       //annoying message, go through some dialogs
       //that you never really wanted to anyway.
        return false;
    }

    public String getName()
    {
        return "$x";
    }
}
So, that's all well and good, until I start refactoring, because the IDEs do not carry on linking the two expansions of $x even after the template has been applied. When I first heard someone rave about IDEA's Live Templates, I assumed they would provide such linking - they do not. Plus, even if they did, you'd still see the generated code, which isn't really that useful. The only part of the above code that's relevant to me is the name of the class and the code inside test. That is, I'd be happy with:

unitTest(testUninstallingDrivers,
{
    stuff..;
    return !card.hasDrivers();
})
..which would generate the above usual Java code. Of course, should I want to see the expanded template, I could just right-click on the template name or something. So what I'd really like from IDEA's Live Templates is for the template's name to stay in my source code, and to be expanded for compilation. But then, if it's staying in my source code, I can't compile without IDEA, or even with IDEA on another computer (unless I successfully manage my project settings). It would be better if the template could stay in my source code too. The source for the template. E.g.:

template unitTest(name,body)
{
    public static final UnitTest <name>=new UnitTest()
    {
       public boolean test()
       {
           <body>
       }

       public String getName()
       {
           return "<name>";
       }
    }
};
This would need a preprocessor before the usual Java compiler. There are some difficulties, like the <body> expansion above generating two sets of braces, but they can be resolved (a syntax like <@body> to splice in the body). In case there's anyone out there who hasn't recognised this yet, I'm really talking about Lisp macros, and not even a significantly complex application of them. I don't see any reason why modern programmers don't use a decent macro system, pretending to be happy with 'Live' Templates, and refactoring everything at a higher level (such as changing the unit test framework to use exceptions) instead of working with the actual source code they like. For a Lispy implementation of a unit test framework (in 26 lines of Lisp!), see this chapter from Practical Common Lisp.

6 comments:

Alex Miller said...

Why not just use $x.class.getSimpleName() ?

burtonator said...

Dude...... you know you can call getClass().getName()

.. right? This works on 100% of the IDEs.

Tony Morris said...

Hi Ricky,
I find it surprising that you have an interest in testing, but reject the use of sound type systems. Type systems ideally eliminate the need for testing altogether. Of course, this ideal is seldom met, even in Haskell (see Coq or Epigram for cases where it is met), where automated specification-based testing is far superior to the "assertion-based" (i.e. explicitly selecting a case from the domain).

You might want to take a look at QuickCheck or, with a touch of my bias, ScalaCheck (of which I am a committer).

Ricky Clarkson said...

You're right, I could use getClass().getName(). I got bitten by making my sample code look more conventional. My real code looks more like this:

public static final UnitTest testInstallingDrivers=new UnitTest(){anonymous class here};

There's no way of grabbing the name testInstallingDrivers at runtime from inside the instance of UnitTest there.

Ricky Clarkson said...

dibblego,

I have an interest in both. I've said on a few occasions that when you find yourself writing more test code than production code, it's likely that you're trying to prove through testing, which is usually nigh-on impossible. In those cases, you should prove through, well, proof.

Where you and I disagree is that you think the proof must be there before the code can be legitimately executed, and Haskell's compiler agrees with you. I'm happy to run code that I haven't thought through the types of yet, and then if I need some assurances that it is correct, I can add types and hence construct a proof later.

I haven't met the ideal language for this yet, though.

Tony Morris said...

Ricky,
That is not exactly my position, but I want pursue it here.

I am wondering (hoping) that this quote from wikipedia might open you to other possibilities:


Traditionally, typed lambda calculi were seen as refinements of the untyped lambda calculus. A more modern view considers typed lambda calculi the more fundamental theory, and untyped lambda calculus a special case with only one type.

...

System F allows polymorphism by using universal quantification over all types; from a logical perspective it can describe all functions which are provably total in second-order logic.

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.