4 Tips for Thinking Functionally

I’ve written before about why I prefer a functional programming paradigm to an object-oriented one, but it’s taken a long time to get there. I dabbled on and off for half a decade before the core ideas behind Clojure and Haskell really sank in and I could write code in my head without having to sit at a computer.

My relationship with functional programming may have been slow, but it was punctuated by several key insights. These deeply altered my approach to designing code, even code in non-functional programming languages.

Here are four habits that dramatically shifted my thinking toward functional programming.

1. Use Functions as Much as Possible

Two years ago, Atomic held an in-house bootcamp for learning Clojure. At the time, I was fairly comfortable with Clojure and especially keen to dive into writing macros. I picked up a copy of Paul Graham’s On Lispand one of the first things I read is that, generally speaking, a Lisp programmer should not write macros. Functions are preferable.

So I put On Lisp away and gave myself a challenge: I would write a simple web application in Clojure and see how much I could do with functions alone. I would resist the temptation to write macros.

That exercise had a profound effect on how I write functions, even in non-functional programming languages. Rather than writing a big function, getting the code working, then hiding the big function behind a macro, I was forced to find the small functions hiding inside the big function. Many of those small functions were general ideas that were useful elsewhere; a macro would not have been so useful.

Today, when I look at a big function or class, I still see the small bits of functionality that are mixed together inside, and I work to pull them out and separate them into distinct, focused utilities. It’s sort of like cooking in reverse.  Rather than mixing ingredients into a batter, I unmix the batter into pure ingredients that can be used in other recipes.

Nelson Morris has a great, concrete example of shifting code from macros to functions.

2. Look at the Source Code

When I first started to investigate functional programming, I couldn’t imagine how certain functions could be implemented without at least some imperative code. For instance, Clojure’s iterate function baffled me. It produces a list of values where each value depends on the previous one. To my imperative mindset, this screamed for a while loop, and I had no idea how one could write it in a functional language.

So I looked at the source code. To my surprise, the iterate function was remarkably straightforward. Here it is in its entirety:

(defn iterate [f x]
  (cons x (lazy-seq (iterate f (f x)))))

Laziness is crucial, but it’s recursion that makes it simple and elegant, even trivial. That was a key moment in my growth as a functional programmer.

I’ve been looking at source code more ever since, though not always as soon as I should have. Not long ago, I needed a function similar to flatten but different. I figured flatten must pretty easy to implement, so without any research, I tried writing the function I needed. It was trickier than I expected. Finally, I surrendered and looked at the source code for flatten.

(defn flatten [x]
  (filter #(not (sequential? %))
          (rest (tree-seq sequential? seq x))))

It was not what I expected. What’s tree-seq? After a bit of research, the function I was trying to write became plain and simple.

Now, I make a habit of looking at the source code, even when I don’t think I need to do so. It’s a great way to learn about new functions and abstractions on which you can build your own code.

3. Learn a Different Programming Language

Java was the first statically typed language I spent much time in, and I struggled with it. Learning a little Haskell gave me a better understanding of static typing in general, and Java code became much more approachable. Haskell’s typeclasses baffled me for a long time, but they made more sense after learning Go’s interfaces. I understood Ruby’s message-passing and JavaScript’s prototypical inheritance better after I learned the Io programming language.

Foreign programming languages have a big impact on programming paradigms. Haskell is a great place to look for interesting and challenging ideas, such as list comprehensions, pure functions, monads, and property-based testing. Whenever I feel stuck with some concept in a functional programming language, I go looking for similar ideas in other languages. Many times, that breaks the mental block and helps me move forward.

4. Talk to Other People

Lastly, talk to other people. The Internet is full of IRC servers and Slack channels and mailing lists for engaging with the community. It’s never been easier to ask questions.

And do feel free to ask them. The functional programming community is excited by new participants and will be happy to help you ramp in. Don’t worry that you’ll ask stupid questions. Everybody starts as a beginner, and the functional programming community understands that better than anyone.