Empty Promises and Other Heresy
How functional programming in Clojure made me a better person
My name is Toni and I’m a programmer
I wrote my first choose-your-own-adventure game when I was eight years old. I had learned to instruct my trusty C64 to wait for input, process the input, print output, rinse and repeat. My first university classes were basically the same, although the process was much slower because my Java code had to be compiled in each iteration. However, we soon ditched any notion of quick prototyping and moved on to Real Programming, which evidently involved lots of modelling, diagrams, classes, objects, and instances.
Now, having used Clojure for a year at work, I’m back to being that kid who enjoyed programming. I write functions that take data as input, do stuff with that data, and output data. That’s it. I can try out stuff in the REPL, look what I got out of it, and try something else. I see the data. I can touch the data. I get immediate feedback. It’s how I have always loved to work.
Reading Clojure code
I will give a few code examples in Clojure. They are brief (Clojure code always is), but you should know the basic syntax. If you already do, come play with us. If you don’t, you could go back and forth to the Clojure cheatsheet, or even take a crash course in Clojure. However, I can tell you the basics in no time. You already know how functions work, right? If so, the following illustration summarizes Clojure syntax for the reader more accustomed to imperative languages:
Now that you know how to read Clojure, let’s get started.
Stuck in the ‘50s
It used to be so that we cared where computers store their data. It made a lot of difference whether the OS loaded your stuff at 0xA5A79 or 0x77F4C (especially if you got this for Christmas). It made even more difference how and when and what memory was accessed or — gasp — manipulated.
Nowadays, most of us don’t think about the day-to-day issues of memory management. “How many bytes does it take to store my integer?” is no longer the right question. “Why should I care?” is a more relevant one. The following code snippets illustrate the difference between describing implementation details and the meaning of data.
Even this simple example shows that data is much more than byte counts. Nonetheless, our de-facto programming languages are still standing on the shoulders of Fortran and C, rooted close to hardware. We are more concerned about the “how” — the exact sequence of operations a computer must perform — than the “whats” and “whys” of programming.
Our focus on the lower levels of computing is in direct contrast to the needs of our customers, for whom declarative will always be greater than imperative. They want to predict when a stone crusher needs maintenance, or guide a customer to book travel that fits their needs and preferences. Beyond meeting the criteria of security and reliability, they don’t (and shouldn’t) really care about the implementation from a business perspective. They need results, not operations, dammit!
A tale of two camps
Programming languages have evolved from two major traditions: the bottom-up camp and the top-down camp. The bottom-up camp approached programming with the imperative style: Let’s start with the hardware-related operations and add abstractions, as long as they don’t sacrifice performance. The more academic top-down camp aimed for mathematical rigor: We’ll ignore performance (for now), and figure out instead, what computing is really about. The approach of the bottom-up camp was detailed control over processing using fine-grained operations, while top-down proponents aimed for simplicity by finding out a small set of powerful constructs.
The bottom-up approach led to object-oriented programming, which in turn led to many sleepless nights refactoring deep inheritance hierarchies and God Objects. Meanwhile, the top-down camp evolved into functional programming, which led to higher salaries and better job satisfaction.
In spite of being separated at birth, these two traditions are now coming close to each other. What used to be the right thing, but terribly slow, is now practical and efficient. Even the most hipster of frontend developers are now embracing functional programming — thanks in large part to React, Flux, and their advocates.
Maybe we could live without a construct for chaining functions like this, or use objects and method chaining (shudder!). However, such deficiencies stack up and encourage a certain style of programming, i.e., not a functional style. For example, the lack of proper function chaining makes lambda lifting quite unattractive.
The hermetic encapsulation
People are celebrating Kotlin for inventing the assignment operation to replace the setters of Java. While it’s certainly better than Java, and Kotlin is embraced by many developers at Vincit, I forget: Why did we need the OO encapsulation in the first place?
The answer is of course: “Someone might do bad things to my data, so I must protect it!” Well, there is a better way. In Clojure, all data is immutable. Once you have it, you can’t change it. I can lay all my data bare in the open, and you won’t be able to mess it up.
The empty promise
Here’s a brief summary of React: Components take input as “props” and output beautiful HTML UIs. When props change, the component re-renders — reactively, you might say. So, when I tried a new visual component library for React, I expected to throw data at the library, and get it rendered. What I got instead, was an empty promise.
The library wanted me to use a promise to signal when my data is ready to be rendered. This was not a good fit, since I was using Redux for my state management and redux-sagafor handling all my side-effects (i.e., things that are not pure; in this case, interact with the unclean outside world). I was forced to write some ugly code that resolved an empty promise (yuck!) after executing the side effects.
Not a big deal, you say? Well, it’s difficult to look the other way, once you’ve seen the light. I’m now used to just managing the data of my app, and getting beautiful reactive UIs by default. The following code example from my re-frame frontend app shows how I update a list of items in response to a :shuffle event:
When my handler changes the in-memory database (a good metaphor for any frontend project, by the way), the views are automatically updated. The changes propagate all the way to the underlying React components, which handle the re-rendering efficiently. No need to explicitly request a re-render, or other ominous trickery.
My first encounter with functional programming felt like a minor head trauma. Trying to read Clojure’s lispy syntax caused my brain to short circuit. However, as the syntax of the language itself promotes a logical structure for Clojure programs, it quickly became understandable. After overcoming the initial parenthesis paralysis, I dared to write a few lines of Clojure myself. Soon, I was able to read Clojure, and even spot some bugs in other people’s code, when they were clear and obvious.
This is how Clojure made me a happier and better programmer. I dare you to challenge your old assumptions and give it a go. There is no need to settle for zero being equal to object plus empty array, and no need to glue on immutable data as a second-class citizen of your codebase. Learn a new language instead, and be happy!
Toni programs in Clojure for fun and profit. He has PhD.