Friday, August 24, 2012

Bringing knowledge from Haskell to Java

Original post at my portuguese blog.

There are many tutorials out there about object oriented programming and test driven development (TDD). The majority uses a simple example: a calculator.

They show the power of polymorphism by building abstractions over binary operations and the value of TDD by doing an incremental test-first code base.

In this post I'll try to extend the calculator's example with a different problem in mind: user interaction.

The code examples given are coded in the most used O.O. language of the globe: Java.

  • Program: Calculator for terminal.
  • Requirement: Read user's input in the format "2 + 2" and show to the user the expected result "4".
  • Problem: How to test-first the user interaction?

As we are making a terminal application, we'll use System.in and System.out to interact with the user. But, we don't want to tie the calculator's algorithm directly to these resources, otherwise it'll be too hard and counter-productive to create and maintain tests for this.

A very important thing that I learned with Haskell is to separate the code into pure and impure areas. A pure code area is a group of functions that depends only on its arguments and produces just one result, i.e. functions that do not modify any information and do not depend on external data. The impure part is composed of the remaining code that depends on I/O, memory read/write, databases, etc.

The Haskell language do an awesome work to assure that this separation holds in the code: its type system makes it impossible to mix pure and impure code. In common languages, like Java, there is no distinction: every piece of code has the freedom to be impure. In consequence, developers have to pay attention to every piece of code that compose the program, taking into account the current state and possible side-effects of every instruction, even from third-party closed code.

In order to set apart the "pure" and "impure" part of our calculator, first we need to be able to identify which functions belong to each part.

All functions that constitutes the calculator's core algorithm may fit in the pure code area, after all they are just representations of mathematical functions, which are pure by definition.

In the other hand, the part that interacts with the user is impure, because it changes and depends upon information that goes beyond its reach: prints characters to the screen and read data from the keyboard.

We should keep the impure part of the code reduced and controlled as it is always the harder part to test. It should do only what it takes to the program to actually work. In our case, the impure part is just the user interaction in the following form:

  1. User types something.
  2. Program shows some text.
  3. Go back to 1.

This interaction is abstract enough to be set totally apart from the code that does the real work. For instance, the following code encapsulates the whole impure part of our calculator:

public interface Program {
  String process(String input);
}

public class TerminalInteraction {
  public void interact(Program program) {
    Scanner s = new Scanner(System.in);
    while (true) {
      String input = s.nextLine();
      String output = program.process(input);
      System.out.println(output);
    }
  }
}

With this isolation, we just made Program to be 100% testable:

assertEquals("4", program.process("2 + 2"));
assertEquals("6", program.process("2 + 2 * 2"));

Also, our calculator's core will only handle Strings, with no direct dependency on System. This means that we can reuse the code for other projects, maybe using it to develop two different interfaces: this, the terminal one, and in the future a graphical one.

The abstraction given as an example has one limitation: every interaction with the user is made through one input line and one output line. Can you enhance it to be possible to have multiple lines as input and output in a single interaction? If you know Haskell, you could imagine something like interact, and if you don't: Learn You a Haskell for Great Good!

1 comment:

  1. Have you looked at what Frege might provide in that respect?

    ReplyDelete