After years of mostly working with object-oriented languages such as Ruby and Python and, as a result, focusing on learning the best practices of object-oriented code design, I’ve recently changed jobs to a company whose applications are mostly written in TypeScript. What’s more, their in-house style avoids classes entirely, preferring a more functional approach to organising the codebase. Although, the principals of good code design are applicable across languages, I felt a little unsure of myself trying to write code in this unfamiliar environment. Therefore, I decided to read up a bit on Functional Programming to learn the specific techniques and patterns of FP that I could use to achieve the nebulous goal of “clean code”. Unfortunately, just as many of the popular OOP books use languages that I can’t be bothered to learn, like Java and C++, many of the top FP books use functional languages, like Haskell and Scala, that I don’t anticipate working with anytime soon. In both of these cases, I have nothing against these languages; it’s just that I’m a practical guy, and if I’m going to put the time and effort into learning programming concepts or techniques, I want to be able to use them. Otherwise, I’ll just forget them, and if I’m going to read something for personal enrichment, I’d rather read a good novel than pour over pages upon pages of code in a language I can only half-understand. Thankfully, there are FP books whose authors have chosen to meet the majority of programmers where they’re at and use JavaScript for their code examples. Professor Fisby’s Mostly Adequate Guide to Functional Programming by Brian Lonsdorf is one such book. Given that it was one of the top results in my searches, and the comments and reviews that I found were generally positive, I decided to read it in the hopes of getting a better handle on how to write good functional code, so that I might contribute to my new job’s functional TypeScript codebase with more confidence.
At 146 pages (according to GoodReads), Professor Fisby’s Mostly Adequate Guide to Functional Programming (MAG from now one for brevity’s sake) is a fair bit shorter than a lot of programming books that I have read. I see this as a strength, because I often find such books to be a bit bloated with extended code examples and in-depth explanations of said code. Sometimes it’s necessary, but often it drags on way too long and probably could have used a hard-headed editor forcing the author(s) to get to the point already. For people looking for a deeper exploration of FP, with more examples to really clarify some of the more-complex mathematical concepts, I can see how this might be viewed as a weakness. I, however, was looking for a quick introduction that would get me writing better functional TS code in short order, so erring on the side of brevity, both in the examples and the explanations of underlying theory, worked well for me. Another overall strength of the book is Lonsdorf’s jokey writing style. Admittedly, the jokes are as likely to elicit a roll of the eyes as a chuckle, but I respect him for trying to keep what can be a very dry topic light and amusing. Yet another reason that programming books often drag at some point (at least for me) is the authors are so concerned with conveying information that they neglect to make their writing engaging, perhaps believing that the content is engaging enough on its own. Now, I’m not expecting Lord of the Rings when learning about how to refactor for-loops, but having a writer with a sense of their own voice, as opposed to an aggressively-neutral presentation of information, makes a big difference in how likely I am to stick with a technical book till the end. One last thing to have in mind about MAG is that, per its own “plans for the future”, it is unfinished. The book is broken into three sections, with the first being a practical introduction to FP syntax and basic concepts, the second going deeper into the theory and using more-abstract structures in the code, and a planned third section that will “dance the fine line between practical programming and academic absurdity”, but which was never added. Given my practical goals for learning from this book, and my reaction to the moderately theoretical second section (more on that below), I don’t view this as a serious omission. MAG does a good job of introducing the techniques and theory of FP, and I imagine if someone wants to really get into the weeds, they’d probably be better off picking up a book that uses one of the pure FP languages anyway.
The first section of MAG, covering seven chapters, serves as an introduction to why FP is useful in codebases and the sort of low-level syntax and structures required to make it possible. Though I was familiar with the concept of pure functions, Lonsdorf’s statement that “The philosophy of functional programming postulates that side effects are a primary cause of incorrect behaviour” struck me as an excellent distillation of the benefits of pursuing FP as the organising paradigm of a codebase. Flaky tests, conflicting component states in React, old invalid records just sitting in the database, all of these are common examples of problems caused by statefulness in software, which we manage via side effects. As I’m sure many of you know, a bug that you can’t reproduce consistently is one of the most difficult to fix, and it’s usually a specific and highly unlikely combination of states that make it so difficult to reproduce. For example, I remember trying to figure out a bug while working at an ecommerce company, where all the products in a user’s cart were available and ready to be purchased when they began the checkout process, but when they tried to pay, the products were no longer available, and we raised an error. After days of pouring over logs looking for clues and trying to recreate the bug anyway I could think of, I finally figured out that the user had opened a second browser tab during checkout and made some changes to their cart before proceeding with payment in the original tab. The cart’s state had changed in one part of our system, but that change hadn’t been propogated to all parts of the system. Now, some statefulness in an application is probably unavoidable, or at least avoiding it would be horribly impractical, but minimisation of dependence on that statefulness greatly simplifies code, because it reduces how much you have to keep track of when writing it. This limits your attention to two things: input and output. Side effects, on the other hand, are theoretically infinite, there’s no limit to the number of database, API, or logging calls you can make in a given function. Therefore, regardless of what language I’m working in, something that I like to keep in mind is that you can use pure functions or methods anywhere, even in largely-OOP codebases. Python and Ruby (and JavaScript for that matter) often offer two variations of a function or method: one that takes an object and changes it, and another that returns a new object (list.sort()
vs sorted(list)
in Python for example). I think that this is one of the most useful lessons from learning about different programming languages or paradigms: you can take the useful pieces from each, mixing and matching them in the code that you write in order to derive some of the benefits of each while mitigating some of the costs.
Now, if one of the great costs of OOP is the pervasiveness of state, what then is the cost of applying FP, which largely avoids state? In my opinion, that would be how abstract and mathematical FP gets once you start wandering down the rabbit hole. I found Lonsdorf’s introductions to currying, function composition, and pointfree style to be useful. These are techniques and syntatical styles that I can use in my own code, I thought. Starting around chapter 7, however, Lonsdorf begins to focus a bit more on some of the theoretical underpinnings of FP in order to introduce higher-level structures that enable the kind of mathematical correctness that adherents of FP promise. At this point, I found myself doing a lot more skimming than I had previously, nodding at the explanations for how functors work and why they are useful, content in getting the gist of it, and not even bothering with the exercises at the ends of the later chapters. The reason for my disengagement is that I didn’t really see myself ever applying these more-advanced techniques or using these more-complex structures in my code. Writing pure functions and composing them with maps, filters, or pipe operators is something you can do in almost any codebase, and the code will likely be easier to read, understand, and change because of it. But functors, applicative or otherwise, well, that’s pretty much an all-or-nothing proposition. Maybe I suffer from a limited imagination, but I don’t see how one could write code in that style in a piecemeal fashion. So, for me, the second half of MAG was pure theory, even when it was explaining the practical application of concepts from set theory. When it comes to code, I’m not particularly interested in theory. I can understand, however, why some coders get inspired by FP and can get so adamant about its benefits. Codebases are messy affairs, containing a few languages, each written in at least a half dozen styles, all based on the momentary preferences of dozens (hundreds? thousands?), of coders that have contributed over the years, and around every corner is a bug, just lying in wait for the right combination of parameters to raise that unanticipated error. So, the idea that your code could have the elegance and provability of a mathematical theorem is a powerful one. It just seems too impractical to me, as we can’t very well expect each new developer who joins our team to spend their first few months reading textbooks on set theory and Functional Programming before they can make their first commit.
One thing that sometimes bothers me about proponents of Agile, OOP, TDD, etc. is that their rhetoric can wander into No True Scotsman territory: it’s not that these techniques or processes or principles are flawed or fail to deliver their promised benefits; people are just doing them wrong. I believe that OOP, done exceptionally well, can provide the kind of readability and maintainability promised by its experts, but it’s really hard to write OOP code at that level. How many coders can claim to be masters with a straight face (or with those around them maintaining a similarly-straight face)? On the other hand, even poorly-written OOP code has some basic organising principles that aids future devs in trying to understand and modify it. You have classes that represent business concepts (sometimes more abstract or technical concepts), and those objects have behaviours represented by their methods. This makes the learning curve much more manageable, because early practitioners have some basic, concrete ideas and techniques that they can apply while they’re still learning the methods for writing truly clean code. My impression of FP is that it’s like the classic bit about learning to draw an owl: make your functions pure, compose them, and now here’s a bunch of set theory to explain why implementing an entire system of functor containers for any data that might pass through your system is totally worth it. The leap from a few basic design principles to abstract structures, without any real-world analog, is large, and I imagine that I’m not the only one who finds that juice to not be worth the squeeze. It feels like it would be easier to write bug-free (or at least lightly-bugged) code if one followed FP to the letter, but sometimes mediocre code is enough to get the job done and move on with your life, and it seems quite difficult to just write mediocre FP code.
I’ve already begun using pointfree style and some light function composition in my code, and being introduced to the JS package ramda
really went a long way toward easing me into a more functional style of coding. I also found that the explanations of functors gave me a better appreciation for what languages like Rust do to avoid unhandled errors and nulls. However, at least for now, I think that’s the extent of the impact of Professor Fisby’s Mostly Adequate Guide to Functional Programming on how I read and write code. Even though I remain unconverted to the full FP path, I feel like I learned some useful concepts and techniques and would definitely recommend this book to anyone who is FP-curious but unwilling to commit to a 400-page tome with code examples written in Haskell.