Clojure Gazette 190: Reusability and Composition

Reusability and Composition


Issue 190 - September 12, 2016

Hello, functional programmers!

How do we make code reusable? Back in the 90s, Object Oriented Programming promised reusability and as an industry, we bought it. We did expand reuse with OOP. Compared with the imperative languages they replaced, OOP brought three things to the table.

  1. Garbage Collection
  2. Polymorphism
  3. Data Hiding

Garbage collection frees libraries and their clients from negotiating about ownership. Who needs to free the memory? If it's you, how will I know when I shouldn't access it any more? These decisions make reuse very difficult. Every library has to define a discipline to follow. Java got this right by using garbage collection. C++ got it wrong.

Polymorphism means that if you have a client of an object, another object that responds to the same messages can be used instead. That means you can reuse the client code without modification.

To maximise reusability, you'll want to minimize the number of different messages you have to respond to. That way, you have less to implement and more things can implement it. Java gets this right sometimes. But generally, the Java type system makes it difficult to work with polymorphism. Designing a small, reusable interface is surprisingly hard.

Clojure does a better job making small interfaces. ClojureScript was able to learn from the small mistakes of Clojure and make the interfaces even smaller. Plus Clojure's protocols let you extend existing classes with new interfaces.

Data hiding can increase reusability, too. If you really capture the essential relationships between the elements of state, your class can become a keeper of a universal truth. But, again, Java dropped the ball and recommended getters and setters. So after all of that data hiding, you just open the door to modifying the data willy-nilly. Mutable state is not so bad if mutation is hidden behind well-defined interfaces (which is how atoms, refs, agents, and vars work), but typical Java does not use data hiding.

The classes in Java also tend to be on the big side. It's very difficult to capture any kind of essential relationship between a large number of changing parts. Two you can handle. Twenty is too many. (So again we see the small size being an indicator of composability.)

We see that OOP does have the potential for quite a lot of reuse, but that it was squandered in Java and the like. Functional programming introduces three more ways to increase composition.

  1. Immutability
  2. Laziness
  3. Lexical closures

Instead of relying solely on data hiding, FP relies on immutable values. Immutable values let you share you values safely with any other part of the code. You don't need to coordinate with the library writer about who is allowed to modify an object. Immutability decouples who can see a value from who can modify information. Software has to manage information like a user's birthday. If I represent that information with a mutable object, I am giving anyone who asks me what my birthday is permission to change it. If it's immutable, they can know my birthday, but they can't modify when I was born.

Laziness is another decoupler. It lets you decouple how to make a collection of values from how many you need to make. The code that produces a bunch of things can be written to make all of them. But they're not made until they're needed by the consumer, which can decide to stop consuming before the end is reached. Clojure's lazy seqs is okay in this regard, but Haskell really shines in this area. Haskell is lazy everywhere by default, not just in lists.

Finally, lexical closures let a function capture values from the scope in which it was defined. It decouples the meaning of a function from where it is used. The meaning is determined by where in the code a function is defined. Lexical closures are one of hallmarks of functional languages. I'm glad to see them in Java 8.

Decoupling is impor tant to composition because decoupling lets you make smaller parts that are at once more general and simpler. Small, general, and simple parts are more composable and reusable. OOP has the potential to make code more reusable, but that potential was not realized by the mainstream due to some missteps of features. FP takes those same things OOP made special and goes further with three tricks of its own. By my count this makes FP have higher potential for creating reusable and composable abstractions. What do you think?

Rock on! is the best place to learn functional programming. At least that's what I'm trying to make it. And I need your help! I need other teachers who want to share the great ideas of the past 50 years of programming and get paid to do it. If you're interested, please watch the invitation video .