DEV Community

Owen Merkling
Owen Merkling

Posted on • Originally published at myowenopinion.com on

Functional Programming Is a Leaky Abstraction

I love functional programming patterns, but why don't pure functional programming languages have more mainstream traction? I believe that it's because they are a leaky abstraction.

What do you mean by pure functional programming?

Functional programming is when an algorithm is expressed using only functions, as opposed to imperative programming where it is expressed using sequential statements. Pure functional programming often adds the restriction that functions cannot mutate data or otherwise have side effects. Functional programming has advantages in being able to reason about what is happening in a program as well as reusability and therefore can help do more with less code, but it is often considered harder to grasp and harder to read because of the way it inverts the reading order of many operations. Sometimes this is pointed to as the reason functional programming doesn't get mainstream adoption (except in mixed-metaphor languages like JavaScript, Python, or Scala), but I think it is because pure functional programming is a leaky abstraction.

What is an abstraction?

A programming abstraction is a conceptual divide between what we manipulate directly, and the end result or what is actual executed. For example, an API is an abstraction between the consumer of a set of data and the production, storage, and modification of that data. Abstractions are often used to create divisions of responsibility within a program. Programming languages are themselves an example of an abstraction. A high-level language hides assembly or byte code, which itself hides machine code. In the end, all languages have to cause different machine codes to be executed by a processor (the end result), but usually you write your program in the highest language, very rarely do you write things in a lower abstraction level.

What makes an abstraction leaky?

An abstraction becomes leaky when how you interact with it depends on its implementation. For example, a file system interface might be implemented either locally or as a remote resource, forcing you to consider latency and network availability effects when that is not an explicit part of the interface. Or a query language abstracts away database access, but two queries that give equivalent results may have drastically different performance. The abstraction was intended to hide part of the system, but you end up having to think about it anyway.

Why is leaking a problem?

Most abstractions end up leaking a little, either because of bugs, or gaps in the specification, or from being used for unintended purposes (Web technologies get that a lot). But when an abstraction leaks so much that it makes the abstraction meaningless, or actually makes coding harder or more error-prone, it becomes a real problem. For example, when Android was created they decided to implement their own version of the Java virtual machine in order to leverage the popularity of Java. But the Android virtual machine implementation introduced severe limits on the number of methods you could have in a single application, which made it difficult to use common constructs, like enums, that generated large numbers of methods. Instead of making it easier to reuse existing code, it forced new programming styles and awkward work arounds for many years.

What does this have to do with functional programming?

It all comes down to one thing: mutable state. Pure functional languages are based in lambda calculus and do not permit side effects or mutation of data, only inputs and outputs. Our current computer systems, however, use the von Neumann architecture, which uses a continuous cycle of read, operate, write steps to execute programs. Fundamentally, all functional languages are translated to an imperative language, and it leaks.

It leaks when you need to read and write files, when you need to respond to real-time user events, when you write to the screen or interact with the GPU, or when you communicate with an external process or API.

Except in the most basic batch operations, computer programs are useless without some form of state change in the system. How else do you read the result? And most interactive applications would be impossible to build without reading and writing files, updating screens, or communicating across a network, all inherently stateful operations.

But what about monads?

Monads are often presented as the solution to stateful operations, but are the largest indicator that the abstraction is leaking.

At a high level, monads represent 'the state of the world' as an input or output to a function. This state is passed around to mark the flow that is causing external interactions. Simple, problem solved. But as a practical construct they tend to be annoying to work with and introduce a lot of boilerplate passing around of monads. Adding a simple logging statement deep in a tree is now the work of changing the entire call stack, or you pass around monads you don't know you need yet. Some languages make special cases for that kind of situation, but then you are back around to leaking.

But I thought you liked functional programming?

I really like using functional patterns. There are many cases where they work better and are just more elegant than object-oriented or imperative patterns. Functional programming can solve a certain class of problems in beautiful ways. But the world is stateful, computers are stateful, and it is already hard to grasp their inner workings. A leaky abstraction just adds to that, and I don't think it will ever be overcome. But I love that so many features of functional languages are moving over, like match statements, first-class functions, closures, and more. I also think that dataflow programming provides a great hybrid, which I plan to write about in a future post.

So here's to functional programming in an imperative world!

Top comments (5)

Collapse
 
pentacular profile image
pentacular • Edited

I don't think that it qualifies as a leaky abstraction by your definition.

While needing to manage effects (i.e., time) explicitly may be annoying and expensive, it doesn't leak implementation details per se.

Your argument might be that the cost of managing this makes using leaky abstractions more attractive, but in that case it would be 'functional programming encourages leaky abstractions' rather than 'is a leaky abstraction', which seems a far more defensible claim.

But I don't think you've really made a good argument for that here either, possibly because you've skipped over the meat of the problem by begging the question. :)

Collapse
 
omerkling profile image
Owen Merkling

Thanks for the feedback. I disagree that this is about encouraging leaky abstractions, it is more the fact that in the end, when you start really debugging, you always end up asking "What order is the chip executing this in?" on some level, as well as "What is the state of the computer (file system, graphics card, network card, etc.) right now?" and that you can't control those outside systems having side effects on each other.

Collapse
 
pentacular profile image
pentacular

Well, in that case you're not debugging the functional system -- you're debugging a procedural system that's implementing it.

And certainly the system that realizes your program (specification) is full of concrete -- that's kind of the point.

So I'm a bit confused as to how or why this is relevant. :)

Thread Thread
 
omerkling profile image
Owen Merkling

That is the point, that functional programming languages continue to lose in adoption to imperative languages because imperative languages match the underlying system better on a fundamental level. Not even a new concept, just trying to present it in a way I thought was interesting.

Thread Thread
 
pentacular profile image
pentacular • Edited

Hmm, I am unconvinced that the reason for this is due to a lack of tooling for debugging the underlying infrastructure.

Let's consider Javascript -- the debuggers generally only debug Javascript and don't do any of the extra things you're talking about.

Yet people find it to be a perfectly reasonable language and it is very popular.

It also has an algorithmic evaluation strategy, where i/o occurs outside the program flow (with the exception of things like nodejs and ajax synchronous calls, but these are pretty exceptional).

If your idea was the reason for a lack of popularity in functional languages you'd expect it to apply here as well, but it doesn't.

I think the true causes are probably (a) network effects, and (b) people are taught mechanical evaluation models from a early age [so, really network effects again].