Faoileag's Nest

My Home On The Net

Home Projects Soapbox Writing About Imprint Data Privacy Statement

Functional Programming - Lessons learned

Look, that's why there's rules, understand? So that you think before you break 'em.
(Lu Tze in Terry Pratchett's "Thief of Time")

Intro

When I started with software development in 1982, the standard paradigm in the home computer world was imperative / procedural programming, with BASIC, Pascal or with assembly language (Z80 in my case).

Then came the 1990s and object-oriented programming became mainstream with Borland Pascal and C++, and C++ is still the language I work with on a day-to-day basis 25 years later (doing gui development with Qt for a living right now).

But I always wanted to go beyond that and understand the third programming paradigm, Functional Programming, as well.

In "Hackers and Painters" Paul Graham praised Lisp as a language you can be extremely productive with when you need to develop bug-free software fast.

And I knew that Emacs plugins were written in Lisp, and functional languages seemed to be on the rise in the new millenium as well: Clojure appeared for the java vm and F# for .NET. So I decided I should give it a try as well, if only to learn one more tool for the trade.

But I didn't get it. Not with Lisp, not with Clojure. The whole functional thing eluded me through multiple attempts to learn the stuff.

Then, during a lockdown in early 2020, I tried again, and this time something went 'click' in my head and I understood. I understood functional programming, I understood enough of Lisp, and I understood the limitations of the paradigma.

This essay is a short summary of what I have learned in that year.

Functional Programming is a paradigm, not a language

On hindsight, not realizing that was probably what kept me from "getting it" on my earlier attempts.

I used to think that for functional programming you have to use Lisp (or a more modern language like Clojure or F#). And that Lisp's unusual notation was a necessity for functional programming. It is not.

Instead, functional programming is a paradigm, meaning that you do functional programming when you adhere to the rules defining that paradigm and not just by using a specific language. In fact, you can use any language you want, as long as it supports the programming style necessary for functional programming. Be that C++, Lisp or Haskell.

Programming in Lisp is not functional programming

Well, at least not automatically. You can do functional programming in Lisp, but the interpreter does not mandate, let alone enforce, it.

You can write great imperative or procedural code in Lisp as well, and it may even look very 'lispy' with lots of brackets, but it is not functional programming.

In fact, it is so easy to write imperative / procedural code in Lisp, that I would regard it as a trap when you come from the object-oriented world and start writing code in Lisp.

No objects in Lisp? Bah, who needs objects. Global variables, and some functions to manipulate them, will provide a good alternative.

Only, that's not functional programming. My first programs in Lisp were like that. And it took me a while (and some further reading) to realize that. But in the end I saw where I had been wrong, and so my current project is done in functional programming. In purely functional programming.

Purely functional programming is taking functional programming that one step further

What's so magic about those six letters purely that they mandate a wikipedia page of their own?

Well, whereas in mere "functional programming" the developer has a lot of leeway to do things, in "purely functional programming" he has not. Instead he has to follow the two basic rules of functional programming religiously. Any deviation and it's no longer purely functional programming.

These rules are:

  1. All functions must always deliver the same result for the same arguments.
  2. Functions must have no side effects.
Simple enough, eh? Only it isn't.

Reading input from the keyboard violates rule one. So does a function like random() that delivers a random number.

Printing something to the console violates rule two, as output to a terminal is considered a side effect.

There you are. Now go ahead and write a meaningful program that adheres to these rules.

You can't write meaningful programs in Pure Functional Programming

Really, you can't. Any meaningful program would have at least some output and that violates the rule about a function not having any side effects.

But of course that doesn't stop people from writing them. And here comes the introductory quote from Lu Tze into play: it's still considered pure functional programming if you break the rules, as long as you break them in the right way.

Haskell is considered a "purely functional language" but even Haskell covers input and output. So what do they do?

In short, Haskell introduces its own rules regarding the breaking of the rules of purely functional programming, and then makes the compiler enforce these rules. And that's why Haskell is considered a purely functional language and Common Lisp is not.

Functional programming is shaping the way you think

Since functions must not have side-effects, and always return the same result for the same input, you have to think about how to handle things like state, and where you handle changes to that state.

We are talking architecture here, and the same application written in purely functional programming will look completely different when written applying the object oriented paradigma.

That is the most profound benefit you might get from functional programming: a different, and often far less convoluted, architecture. Colleges chose Lisp for a reason when it was used to introduce students to programming. With e.g. BASIC you can hack straight away; with Lisp you have to think.

Paul Graham chimes in here as well: in "Hackers and Painters" he writes that he considered it a business advantage that their codebase had been written in Lisp, as maintanance was much easier.

Functional programs are easier to test for correctness

The second benefit you get from functional programming arises from the fact that functions must not have any side effects: it is much easier to write unit tests for functions that adhere to the principles of purely functional programming.

In object oriented programming, side effects are a fundamental part of the paradigm: a method used and / or modifies the data stored in the object's member fields.

But each member field used in a method counts as an argument to that method call, and the number of unit tests you have to write to cover all eventualities grows exponentially with the number of arguments.

In functional programming, you don't have these hidden arguments. And you normaly try to keep the number of visible arguments small.

Meaning you need fewer unit tests to reach 100% of code coverage. Meaning it is easier to test your application for correctness.