Object-oriented – How to refactor an OO program into a functional one

functional programmingobject-orientedrefactoring

I'm having difficulty finding resources on how to write programs in a functional style. The most advanced topic I could find discussed online was using structural typing to cut down on class hierarchies; most just deal with how to use map/fold/reduce/etc to replace imperative loops.

What I would really like to find is an in-depth discussion of an OOP implementation of a non-trivial program, its limitations, and how to refactor it in a functional style. Not just an algorithm or a data structure, but something with several different roles and aspects – a video game perhaps. By the way I did read Real-World Functional Programming by Tomas Petricek, but I'm left wanting more.

Best Answer

Definition of Functional Programming

The introduction to The Joy of Clojure says the following:

Functional programming is one of those computing terms that has a amorphous definition. If you ask 100 programmers for their definition, you’ll likely receive 100 different answers...

Functional programming concerns and facilitates the application and composition of functions... For a language to be considered functional, its notion of function must be first-class. First-class functions can be stored, passed, and returned just like any other piece of data. Beyond this core concept, [definitions of FP might include] purity, immutability, recursion, laziness, and referential transparency.

Programming in Scala 2nd Edition p. 10 has the following definition:

Functional programming is guided by two main ideas. The first idea is that functions are first-class values... You can pass functions as arguments to other functions, return them as results from functions, or store them in variables...

The second main idea of functional programming is that the operations of a program should map input values to output values rather than change data in place.

If we accept the first definition, then the only thing you need to do to make your code "functional" is to turn your loops inside out. The second definition includes immutability.

First Class Functions

Imagine you currently get a List of Passengers from your Bus object and you iterate over it decreasing each passenger's bank account by the amount of the bus fare. The functional way to perform this same action would be to have a method on Bus, maybe called forEachPassenger that takes a function of one argument. Then Bus would iterate over its passengers however that is best accomplished and your client code that charges the fare for the ride would be put in a function and passed to forEachPassenger. Voila! You're using functional programming.

Imperative:

for (Passenger p : Bus.getPassengers()) {
    p.debit(fare);
}

Functional (using an anonymous function or "lambda" in Scala):

myBus = myBus.forEachPassenger(p:Passenger -> { p.debit(fare) })

More sugary Scala version:

myBus = myBus.forEachPassenger(_.debit(fare))

Non-first-class Functions

If your language does not support first-class functions, this can get very ugly. In Java 7 or earlier, You have to provide a "Functional Object" interface like this:

// Java 8 has java.util.function.Consumer, but in earlier
// versions you have to roll your own:
public interface Consumer<T> {
    public void accept(T t);
}

Then the Bus class provides an internal iterator:

public void forEachPassenger(Consumer<Passenger> c) {
    for (Passenger p : passengers) {
        c.accept(p);
    }
}

Finally, you pass an anonymous function object to the Bus:

// Java 8 has syntactic sugar to make this look more like
// the Scala solution, but earlier versions require manually
// instantiating a "Function Object," in this case, a
// Consumer:
Bus.forEachPassenger(new Consumer<Passenger>() {
    @Override
    public void accept(final Passenger p) {
        p.debit(fare);
    }
}

Java 8 allows local variables to be captured the scope of an anonymous function, but in earlier versions, any such varibales must be declared final. To get around this you may need to make a MutableReference wrapper class. Here's an integer-specific class that lets you add a loop counter to the above code:

public static class MutableIntWrapper {
    private int i;
    private MutableIntWrapper(int in) { i = in; }
    public static MutableIntWrapper ofZero() {
        return new MutableIntWrapper(0);
    }
    public int value() { return i; }
    public void increment() { i++; }
}

final MutableIntWrapper count = MutableIntWrapper.ofZero();
Bus.forEachPassenger(new Consumer<Passenger>() {
    @Override
    public void accept(final Passenger p) {
        p.debit(fare);
        count.increment();
    }
}

System.out.println(count.value());

Even with this ugliness, it is sometimes beneficial to eliminate complicated and repeated logic from loops spread throughout your program by providing an internal iterator.

This ugliness has been fixed in Java 8, but handling checked exceptions inside a first class function is still really ugly and Java still carries the assumption of mutability in all its collections. Which brings us to the other goals often associated with FP:

Immutability

Josh Bloch's Item 13 is "Prefer Immutability." Despite common trash talk to the contrary, OOP can be done with immutable objects, and doing so makes it much better. For instance, String in Java is immutable. StringBuffer, OTOH needs to be mutable in order to build an immutable String. Some tasks, like working with buffers inherently require mutability.

Purity

Each function should at least be memoizable - if you give it the same input parameters (and it should have no input besides its actual arguments), it should produce the same output every time without causing "side effects" like changing global state, performing I/O, or throwing exceptions.

It has been said that in Functional Programming, "some evil is usually required in order to get work done." 100% purity is generally not the goal. Minimizing side effects is.

Conclusion

Really, of all the ideas above, immutability has been the biggest single win in terms of practical applications for simplifying my code - whether OOP, or FP. Passing functions to iterators is the second biggest win. The Java 8 Lambdas documentation has the best explanation of why. Recursion is great for processing trees. Laziness allows you to work with infinite collections.

If you like the JVM, I recommend you take a look at Scala and Clojure. Both are insightful interpretations of Functional Programming. Scala is type-safe with somewhat C-like syntax, though it really has as much syntax in common with Haskell as with C. Clojure is not type-safe and it is a Lisp. I recently posted a comparison of Java, Scala and Clojure with regard to one specific refactoring problem. Logan Campbell's comparison using the Game of Life includes Haskell and typed Clojure as well.

PS

Jimmy Hoffa pointed out that my Bus class is mutable. Rather than fix the original, I think this will demonstrate exactly the kind of refactoring this question is about. This can be fixed by making each method on Bus a factory to produce a new Bus, each method on Passenger a factory to produce a new Passenger. Thus I've added a return type to everything which means I'll copy Java 8's java.util.function.Function instead of Consumer interface:

public interface Function<T,R> {
    public R apply(T t);
    // Note: I'm leaving out Java 8's compose() method here for simplicity
}

Then on Bus:

public Bus mapPassengers(Function<Passenger,Passenger> c) {
    // I have to use a mutable collection internally because Java
    // does not have immutable collections that return modified copies
    // of themselves the way the Clojure and Scala collections do.
    List<Passenger> newPassengers = new ArrayList(passengers.size());
    for (Passenger p : passengers) {
        newPassengers.add(c.apply(p));
    }
    return Bus.of(driver, Collections.unmodifiableList(passengers));
}

Finally, the anonymous function object returns the modified state of things (a new bus with new passengers). This assumes that p.debit() now returns a new immutable Passenger with less money than the original:

Bus b = b.mapPassengers(new Function<Passenger,Passenger>() {
    @Override
    public Passenger apply(final Passenger p) {
        return p.debit(fare);
    }
}

Hopefully you can now make your own decision about how functional you want to make your imperative language, and decide whether it would be better to redesign your project using a functional language. In Scala or Clojure, the collections and other APIs are designed to make functional programming easy. Both have very good Java interop, so you can mix and match languages. In fact, for Java interoperability, Scala compiles its first class functions to anonymous classes that are almost compatible with the Java 8 functional interfaces. You can read about the details in Scala in Depth sect. 1.3.2.

Related Topic