I’ve been asked at least once “How can I get better at programming in a compositional style, with functions?” This question usually stems from someone trying to learn a language dedicated to functional programming and struggling to make the paradigm feel “natural”. As one of the more outspoken advocates of functional programming in the office, I thought I’d provide a “listicle” of tips in the inimitable style of BuzzFeed to address the question.
1) Avoid libraries that do it all for you
Functional programming is all about building up complicated solutions from small, easy to understand parts. Getting better at this means avoiding the monolithic, magical frameworks and libraries that do everything for you in favor of assembling all the parts of a problem yourself. You don’t need to work in a fancy language like Clojure or Haskell to get better at this either (though it doesn’t hurt!). For example, instead of choosing Rails for your next web project, try going with something lighter like Sinatra and filling in any missing pieces yourself. This forces you to really think about the requirements of the problem you’re solving and consciously find, write and glue together the parts that make a good solution.
2) Use languages designed for compositional programming
Programming languages are designed to make certain tasks easy. To do this, trade-offs need to be made such that other tasks are more difficult. Every language makes different trade-offs in this space. Functional programming languages like Haskell or Clojure make it very easy to define small functions and compose them to make larger blocks of functionality. On the web programming side, Elm is a Haskell inspired language that promotes many of the same best practices and it compiles to JavaScript.
3) Use something to keep you consistent
When you write in a compositional style your program ends up being the sum of many interconnected parts. Values flow through those parts to make interesting work happen. Just as a ten-inch pipe and a twelve-inch pipe have different jobs, and you can’t connect them without an adapter, you can’t expect all those interconnected functions to work without some adapters. A type system, static analyzer, or extensive unit tests can help you quickly find where you need those adapters. Haskell has a solid type-system, Erlang/Elixir has a static analyzer in Dialyzer, and Clojure has a type system in core.typed as well as spec which can automatically generate unit tests for your functions.
4) Remember that functions map sets of values to sets of values
Fundamentally, a functional programming language works with functions. These work much more like mathematical functions from algebra as opposed to working like procedures from a language like C or Python. These functions take a specific set of values as input and return a specific set of values as output. Which values it returns as output given specific inputs defines the behavior of the function. As an example, consider a function that returns the length of a string. As its input set it can receive any string, and as its output it can return any positive integer including 0. It can’t return a negative number, nor can it return floating point numbers. Additionally it’s behavior isn’t defined if you give it something different, like a boolean value. Keeping this principle in mind will help you gain an intuition for when your compositions will “just work.” Oh, and a type checker helps here too.
5) Think about the properties of your functions
There are all sorts of properties a function can obey. These can be internal to the function itself, defining its behavior in isolation, or they can be external, defining the behavior in relation to other functions. Internal properties are going to determine which inputs map to which outputs, and consequently define the behavior of the function. External properties are going to make sure your function doesn’t have surprising behavior or edge cases in compositions. An example of an internal property is referential transparency, or the function returns the same output when given the same input. An example of an external property is associativity, or can I evaluate the function in any order I like?
6) Abstract mathematics is your friend
I know abstract mathematics can be a bit daunting to get into, but mathematicians have been studying how to compose things together for hundreds of years. You can treat the hard-won concepts they’ve discovered as design patterns to apply in your own code. There are a couple of resources I’d recommend for learning more about these patterns. The FantasyLand project offers specifications in JavaScript for many concepts. The Typeclassopedia is Haskell specific and goes over a smaller number of the concepts but includes more thorough explanations and motivations behind them. Finally, the LambdaCast podcast includes topics on some of the specific concepts, as well as more general topics in functional programming. The names of these concepts can be a bit of a mouthful sometimes, but the terms have tons of history and research behind them, so it is quite easy to have a conversation about them or google more learning material when necessary.
And finally...
Most of these tips aren’t specific to languages designed for functional programming. Being able to build large systems out of smaller, well-understood parts is a valuable skill in any paradigm. Getting good at it will not only improve your functional code, but it will also improve the way you write object-oriented code as well. Use these tips to guide the way you practice and we’ll all write safer, easier to understand code.