Thursday, February 3, 2011

Flash Cards: T-P Premise

The flash card kata is pretty straightforward. Given a set of questions and answers, ask the user each question and solicit an answer. If the answer is correct, say so and count it. If the answer is wrong show the correct answer and count the error. At the end, print the number of right and wrong answers.

The first test is wonderfully simple. Given an empty list of questions and answers, end the game immediately with a summary showing nothing right and nothing wrong.

This fails of course. We can make it pass by simply incrementing the right count in playGame if the list of cards is not zero. This is a (unconditional->if) transform. That, plus a little refactoring gives us:

This forced us to create the answerQuestion function that pretends to be a user answering questions. If you pass in “QR” you get the right answer “A”. If you pass in “QW” you get the wrong answer “W”. To get this test to pass we’re going to have to get this function called by playGame. We can do this by passing the test along in an argument using the Change Signature refactoring. Then we can use a (unconditional->if) transform to check the value of our new function.

There’s more to do, of course, but the plumbing is all set up, and the algorithm looks right. There were several cases where we could have used a lower transform such as (variable->assignment) but there was no need, and the algorithm came out nicely.

There is just the slightest chance that the one use of (if->while) could have been done with (statement->tail-recursion), but since this is Java, that’s probably not the best choice.

Obviously this is the wrong approach, but the priority list presented in my original article did not prevent it. So I’ve added the (case) transformation to the very bottom of the list. This means that using a switch/case or an ‘else if’ is always the last option to choose.

Guilherme went on to rightly ignore the switch/case solution to see if he could get a good solution for fib by following the other priorities. I suggest you read his blog to see how that turned out. Meanwhile, let’s try it here.

The first test leads us to use the ({}–>nil) and then the (nil->constant) transformations:

publicclassFibonacci { publicstaticint of(int n) { return0; } }

The second test forces an (unconditional->if) transformation that we can refactor with a (constant->scalar). This coincidentally makes the third test pass which is always nice.

publicstaticint of(int n) { if (n <=1) return n; return1; }

The fourth tests is tricky. How can we transform that ‘1’ into something that maps 1->1, 2->1, and 3->2. We know that fib(n) = fib(n-1)+fib(n-2) so we could use recursion to solve the problem. That’s the (statement->recursion) transformation.

This makes all the tests pass. Hallelujah! And look how simple that was! What a pretty sight.

Unfortunately there are three things wrong with this pretty solution. First, that algorithm has a horrific runtime complexity of something like O(n2) or worse. Secondly, the algorithm does not use tail-recursion, and so uses a lot of stack space. Thirdly, Java is not a great language for recursion anyway since the JVM simply can’t optimize tail recursion (yet).

It’s a great shame that such a simple expression has so many problems! There are ways to address that, but they are beyond the scope of this article. For now we’ll focus on the three problems mentioned above.

Let’s tackle them one at a time. Is there a transformation that will at least get us to tail recursion? Of course there is, but it was missing from my original list. So I’ve modified that list as follows:

({}–>nil) no code at all->code that employs nil

(nil->constant)

(constant->constant+) a simple constant to a more complex constant

(constant->scalar) replacing a constant with a variable or an argument

(statement->statements) adding more unconditional statements.

(unconditional->if) splitting the execution path

(scalar->array)

(array->container)

(statement->tail-recursion)

(if->while)

(statement->recursion)

(expression->function) replacing an expression with a function or algorithm

(variable->assignment) replacing the value of a variable.

(case) adding a case (or else) to an existing switch or if

So tail recursion is preferred over arbitrary recursion.

Now, can we use tail recursion to tranform this?

publicstaticint of(int n) { if (n <=1) return n; return1; }

Of course we can. It’s not as pretty as the previous solution, but it captures the same semantics. And it’s not ugly by any means.

But now, how do we deal with the fact that Java doesn’t do well with recursion? If we thought that n would always stay relatively small, we could just ignore it. But let’s assume that ‘n’ will be large; forcing us to unwind the recursion and replace it with iteration. This requires a (if->while) and a few (variable->assignment) transformations.

The list of priorities prevents this from being the direct outcome of TDD because it prefers the recursive solution. So my list of proposed priorities will necessarily create Java programs that are recursive, and therefore less than optimal for the language.

That makes me think that the priority list is language specific. In Java, for example, we might move (if->while) and (variable->assignment)above(statement->tail-recursion) so that iteration is always preferred above recursion, and assignment is preferred above parameter passing.

This makes sense because Java is not a functional language, and strongly resists a functional style. So any bias towards functional style will lead to suboptimal implementations.

If the priority list is language specific, is it also application specific? Do different problem domains require different priority lists? I strongly doubt this. We are working at a level of detail so far below the problem domain that it is hard for me to see how different problems would require different solution styles.

What about teams? Will teams tweak the priority list to match their styles? I hope not; but I have to say that I think this is not improbable.

I think what this shows us is that the transformations and their priorities are a way to encode a particular programming style. So long as we have different languages and styles, we’ll likely need different transformations and priorities.

On the other hand, if we compare the Java list with the Clojure list (say), the difference is subtle. The recursive transformations would move slightly lower in the list relative to the iterative and assignment transformations. The effect is, of course, profound, but the difference in the lists is actually relatively small. All the other transformations seem to hold their positions.

So the good news is that, although there may be different styles based on language type, the vast majority of the low level coding decisions remain similar irrespective of those styles.