Is functional programming declarative?

This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.

Subscribe: RSSApple PodcastsGoogle PlayOvercast

People often say that functional programming is a "declarative paradigm". I push back against that categorization. I simply think the word is mostly meaningless.

Transcript

Eric Normand: Is functional programming declarative? Hi, my name is Eric Normand. These are my thoughts on functional programming.

When people describe functional programming, one thing that comes up a lot is saying that functional programming is a declarative paradigm. To further explain what declarative means, they'll say, "It means you say what you intend to do and what you want without telling how you do it, see?"

I want this list transformed. You don't say how to do it, which is like giving a for-loop with an increment variable that you go through the loop, stop at the end of the list, etc.

Now I want to challenge this whole idea. I don't think it holds water. It might be a good, intuitive idea that FP is somehow better at expressing some problems because it's declarative. I just don't like the idea in general.

The reason I don't like it is every paradigm claims to be declarative. It can, if you take declarative as a relative term. When I do a for-loop in even something like C, that's declarative compared to assembly. In assembly, I have to use a go-to. I have to say, "How do I loop? What line of code do I jump to? What has a label? I'm going to jump to it after I do this comparison."

https://twitter.com/ericnormand/status/1040254232383901701?ref_src=twsrc%5Etfw

If you go down to something like machine code, you might even be choosing what register you're storing your increment variable in. Looping becomes a design pattern for a certain pattern of using go-tos, increment variables, and stuff like that.

The idea that you're telling it what you want and not how to do it, every paradigm or every language that is better than the last, more expressive than the last can claim that.

I just think that it's like a meaningless word. Now you can say that it's a relative term that for certain constructs, let's say, a map, or a filter, or a reduce. You can say that that is more declarative than a for-loop. It's only relative. To use the term declarative in an absolute sense is meaningless. To say functional programming is a declarative paradigm.

It's just not. It doesn't mean anything because in 20 years, 30 years, there might be an improvement, a better paradigm. Someone is going to claim that that one is declarative. They're going to compare it to functional programming. They're going to say, "See, it's way more declarative than that one."

I just think we should stop using this term, at least, in an absolute way. Maybe, you can use it in a relative way. The only thing it means in a relative way is the details that I have to worry about are taken care of for me. What's the name of the index variable as I loop through this? That's gone.

That's something you have to care about in the for-loop, but you don't have to care about that when you're doing map. Just like in a for-loop, you don't have to care about what register you're storing your stuff in. That's taken care of for you by the for-loop and the compiler.

Another thing people will say is that you don't have to worry about how the map works. That you could say, "Oh, that map could be totally replaced by the compiler with a parallel implementation or some other implementation."

https://twitter.com/ericnormand/status/1039529434066415616?ref_src=twsrc%5Etfw

Now this is sometimes true, that a certain construct will somehow insert itself and maintain the semantics that it promises while somehow optimizing it.

We see this in SQL a lot. You send a SQL statement. We know that the SQL will be optimized by the database. The order that we put the joins in isn't really the order that stuff will be executed in. There's going to be an optimization step.

This, again, is simply not unique to what we would call "declarative languages." The C++ compiler, the GNU C compiler, they all have optimization steps.

The JIT, the Java just-in-time compiler, is going to insert itself into the process, rearrange your operations, and just do what it wants with it. It's supposed to maintain a certain semantic. Sometimes, that's not intuitive of what it's going to be doing.

Also, that this idea that declarative gives the compiler leeway to optimize, it's only a relative term. Maybe, Haskell is more optimizable than C because it is more declarative. It's at a higher level. It's a relative thing, again. It's not about, "Haskell is declarative and C is not."

https://twitter.com/ericnormand/status/1039529434066415616?ref_src=twsrc%5Etfw

I really think that we need to stop [laughs] using this. If anything, it's only a way to provoke people to push further. Even map, if you have a given language, the idea of map is concrete. It is not declarative. You can look up the definition of map in your language. In Clojure, you can just look it up in the code. In Haskell, you can just look it up in the code.

It's fixed. You know how it works. It executes certain known deterministic steps to achieve its aim, to achieve what the semantics of that operation are. To claim that it's declarative in some way, as opposed to, say, a for-loop, which you can say, "Well, it's going to compile to this go-to." All of that is the same. It's exactly the same.

If it means anything, it simply means that map has a semantic, has a meaning that is robust enough that you can consider it conceptually opaque, meaning you don't have to think about how it's implemented. It's clear enough and it works without corner cases enough that you just don't have to worry about it anymore.

There's a lot more to worry about with a for-loop like, "Did I initialize my variable? Did I increment it? Am I missing an off-by-one error in my termination condition?" There's all these things you have to worry about. You're not dealing with a problem as directly.

Map lets you forget about all those problems. It has no corner cases. It deals with the MD list just fine. It deals with an infinite list just fine. It's simply easier to wrap your head around. That might be what people are trying to get at when they talk about it being declarative.

https://twitter.com/ericnormand/status/1039167140706873345?ref_src=twsrc%5Etfw

It's conceptually opaque. You can forget how it actually works if you choose to. You can do what you're supposed to be able to do with good object-oriented programming, which is consider only the interface and forget about the implementation.

In that same way, good object-oriented programming is declarative. You see, this term is not that meaningful. I think we should stop using it.

Map is actually more concrete than a for-loop. You can do a lot of stuff with a for-loop. It is an abstract idea. There's an initialization step. There is a termination condition. There is a thing that happens on every iteration. Then there's a body. That's a very general construct. You can do a lot with that.

https://twitter.com/ericnormand/status/1039227371793121280?ref_src=twsrc%5Etfw

A map, on the other hand, is only about transforming one list into another. It can't change the number of items in the list. It can only apply the same operation to every item in the list. A for-loop, you could step by three. You could skip things, or you could terminate early and not hit the end of the list. There's all sorts of stuff you could do. You can't do that with map.

However, a map is more concrete. It's more abstract than the particular for-loops that do implement that same pattern, that pattern of transform everything in one list into another list. It is more abstract because it's eliminating a lot of the details. You don't have to name the index variable. You don't have to worry about the termination condition.

You don't have to worry about incrementing the variable on each step. It is more abstract than those particular patterns of usage. I'm not saying declarative. It's just more abstract. You're not worried about those details. You're elided details. That's what makes it abstract. Abstract, concrete — that's not what's important.

What's important is that it gets you closer to the problem domain. By eliding those details, it lets you focus more on solving the problem. It is one step closer to speaking about the particular domain. It's just one step. It's just one step closer to speaking about the domain you're interested in as opposed to speaking about incrementing integers and indexing that into an array.

That's my rant on declarative programming. This is just my opinion. I'm sure that there are people who will defend the idea of it being declarative. There's also people out there who have real definitions of declarative, and I've seen them. I don't quite understand them. It has to do with whether the operations that a language performs are deterministic at all.

In most functional languages, they are deterministic. If you knew all of the rules that the compiler was following, you could just figure out what operations were going to happen in what order. Even with lazy evaluation and everything, they're all very deterministic.

I'd love to continue this discussion on Twitter. I'm @ericnormand. You can also email me at eric@lispcast.com. Let's get into this. Let's get into this discussion because I really think we should stop using the term "declarative" to describe functional programming. It doesn't hold water.

Thanks so much. See you later.