Functional languages are heavily imperative. A developer argues that functional programming languages like Haskell are inherently imperative, citing academic papers from the 1990s that introduced imperative features to Haskell. The post highlights that functional and declarative are independent dimensions, and that code can be both functional and imperative, as demonstrated by Haskell's IO monad and lazy imperative programming. This article will seem full of contradictions. Even writing it feels like a contradiction: Why am I digging through academic Haskell papers from the '90s right in the middle of my AI psychosis moment? Literally, as I write this, I am running my first experiment with an agent delegating tasks to another agent. So why am I spending time on esoteric academic nonsense instead of giving in to the vibes and building my loopy graphy software factory? I am writing this because AI is a nuclear amplifier on the patterns we establish in our codebases, and we literally do not have the right terminology to establish scalable patterns. Nobody has the answers we need because nobody even has the right terminology to talk about it. Industry dogma says that functional and declarative are synonyms—that code is either side-effectful and imperative, or pure and declarative: But functional and declarative are independent dimensions: Code that is both functional and imperative debunks this, and so does code that is both side-effectful and declarative. Both exist Simon L Peyton Jones and Philip Wadler helped create Haskell, which is a beautiful, pure functional programming language. In 1993 they wrote a paper called "Imperative Functional Programming." https://dl.acm.org/doi/epdf/10.1145/158511.158524 The title may sound like an oxymoron, but it isn't. The functional programming community had been struggling with how to manage side-effects like I/O, and these co-creators of Haskell proposed a way to decouple the expression of I/O commands from their execution . But they noticed something: It will not have escaped the reader’s notice that programs written in the monadic style look rather similar to imperative programs. For example, the echo program in C: echo { loop: a = getchar a ; if a == eof return; else { putchar a ; goto loop; } } In Haskell: php echo :: IO echo = getcIO 'bindI0' \a - if a == eof then doneIO else putcI0 a 'seqI0' Does the monadic style force one, in effect, to write a functional facsimile of an imperative program, thereby losing any advantages of writing in x functional language? Nope Jones and Wadler list two advantages that remain: map and append can still operate on monadic commandsRecognizing the imperative structure of the Haskell monadic pattern does not mean we have to abandon the advantages that are inherent in pure functional programming. But it is still imperative structure. John Launchbury, another creator of Haskell, wrote a paper the following year called Lazy Imperative Programming. https://www.researchgate.net/publication/2248360 Lazy Imperative Programming In it he says, Imperative features were introduced to Glasgow Haskell for expressing input and output. Could this be plainer? Haskell has imperative features. And the Haskell code that uses them is imperative. Also from his paper, an "Imperative Scan Left": writeVar is an imperative statement, which is why this is an example of imperative code—written entirely with pure functions The paper https://www.researchgate.net/publication/2248360 Lazy Imperative Programming has many other references to imperative stuff, including this: In a strict imperative framework such as the IO monad and most imperative languages , no value could be returned until the whole of the list was traversed. Using lazy sequences, however, this is not the case. If only the head of the list is required then very little of the computation is performed: the variable is allocated and initialised, it is read, and the list returned with that value in the head. If even less is required, merely whether the final list is empty for example, then the variable is not even allocated as only xs needs to be examined in order to give the structure of ys. 🤯 This is insanely cool. Again, not everything in Haskell has to be declarative for it to still allow for incredible execution properties Paul Hudak another creator of Haskell and Conal Elliot wrote a paper https://dl.acm.org/doi/pdf/10.1145/258948.258973 in 1997 where they weren't happy with the "strongly imperative feel" of Concurrent Haskell—even though it was 100% purely functional. Side note: I owe my passion for programming to this paper. More on that later. Here's an example of what that pure functional approach with an "strongly imperative feel" looked like: bash box <- newEmptyMVar forkIO $ do threadDelay 1000000 putMVar box 42 result <- takeMVar box puTMVar box 42 expresses a command to set box 's value to 42. In the authors' own words, While this system is purely functional in the technical sense, its semantics has a strongly imperative feel. That is, expressions are evaluated without side-effects to yield concurrent, imperative computations, which are executed to perform theimplied side effects. In contrast, modeling entire behaviors as implicitly concurrent functions of continuous time yields what we considera more declarative feel. It is not a coincidence that the definition of "imperative" in English is "expresses a command" and that a line of code that literally "expresses a command" had an "strongly imperative feel"—even in Haskell. But the authors were dissatisfied with that "imperative feel"—so they invented functional reactive programming FRP : js box t0 = 0 untilB predicate time = t0 + 1 t0 -= 42 Just a single declaration. Nothing here expresses a command at all. putMVar is 100% imperative, while this is 100% declarative and 0% imperative. Since the industry dogma in the 1990s was "functional = declarative", they had to call FRP more declarative. Concurrent Haskell was already declarative, supposedly, by virtue of being in Haskell—so this had to beextradeclarative 🤷And I say that with great admiration for these authors, because I owe my passion for programming to this paper. This was the origin of both RxJS and signals, which I love... very much. Too much for many people. The "Lazy Imperative Programming" paper was more contradictory. The author often contrasted Haskell with "imperative languages," while dozens of times referring to features withinHaskell as imperative.On the one hand, functional languages are commonly more expressive and easier to reason about than imperative languages, After reading 20 or so papers from the 1990s talking about programming paradigms, you will understand that the terminology was extremely entrenched. Academics classified programming languages into rigid categories to help explain the programming patterns of the time. Functional programmers were especially guilty of reinforcing this reductionism, since they were a minority of programmers and had to fight against the grain with every rhetorical tool they could. Branding functional programming languages as automatically 100% declarative was an enticing selling point—even though we have many Haskell creators very plainly referring to features withinHaskell as imperative.Also, in the 1990s very few "imperative languages" had added much functional features, having not had to deal with much concurrent processing yet, which meant languages really did mostly fall into neat categories; multi-paradigmatic languages were not the norm like they are now. The concept of entire languages as "imperative" or "declarative" somewhat applied back then, but today is a nothing but a leftover from decades of synchronous code that all matched the same 2-3 structural patterns. We have to make a mental note of this history, but if we do not push to correct the terminology, it will continue to confuse developers and AI. Many developers think that stuffing imperative features between < characters magically makes them declarative because it's part of HTML, a "declarative language." Some are attempting this and creating masses of spaghetti code that only cosmetically looks different from the imperative JavaScript spaghetti they were writing last year.There are no "declarative languages" and "imperative languages", as much as it would simplify selling them. There are commands expressed in code, which are imperativeby the purest definition; and there are features and behaviorsdeclaredwithout scattered commands controlling them from elsewhere. The literal precise English definition of "imperative" is something that expresses a command. That is what these pure functions are doing in Haskell and other functional languages. This should already rest the argument. But even the less precise definitions of imperative programming focus on structural descriptions written in code rather than execution details: It's very clear that Haskell and functional programming languages are extremely cool with the flexibility they afford for all kinds of things, including the execution of side effects. But Haskell has features that enable writing literal imperative commands even with pure functions. This results in code that is structured imperatively: Explicit commands incrementally adding to behavior described elsewhere. The instant a single command appears, it completely takes away the declarative quality of describing the final result of something up-front. The imperative features of Haskell are not just less declarative; they are completely anti-declarative. Functional programming can be imperative. RxJS enables fully declarative code, despite latent side-effects. RxJS actually came from that Fran paper we looked at earlier, which proposed a way to turn this kind of Concurrent Haskell: bash box <- newEmptyMVar forkIO $ do threadDelay 1000000 putMVar box 42 result <- takeMVar box into this: js box t0 = 0 untilB predicate time = t0 + 1 t0 -= 42 Neither of these trigger side-effects, but the declarative version structures the code as a single description of a complete, final logical result a behavior over time . The thing that made the first imperative and the second declarative is the structure of the code, not the side-effect execution model. The same structural move can be made in side-effectful code: js const box = Promise.withResolvers ; setTimeout = { box.resolve 42 ; }, 1000 ; const result = await box.promise; js const box = await lastValueFrom timer 1000 .pipe map = 42 , ; Both of these trigger side-effects when executed, but the declarative version structures the code as a single description of a complete, final logical result. The thing that made the first imperative and the second declarative is the structure of the code, not the side-effect execution model. These four code snippets alone are enough to complete our four quadrants: But let's look at some more common declarative side-effects. "HTML is declarative" is often said. And it's mostly true see above callout "Note on "more declarative" and monoparadigmatic dogma" . Yet the entire point of HTML is a side-effect: You being able to look at beautiful interfaces like this: It's not just the DOM that's a side effect though. The HTML rendered in the page can itself trigger other kinds of side effects, like network calls: