{"slug": "functional-languages-are-heavily-imperative", "title": "Functional languages are heavily imperative.", "summary": "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.", "body_md": "This article will seem full of contradictions.\n\nEven 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?\n\nI 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.\n\nIndustry dogma says that functional and declarative are synonyms—that code is either side-effectful *and* imperative, or pure *and* declarative:\n\nBut functional and declarative are independent dimensions:\n\nCode that is both functional and imperative debunks this, and so does code that is both side-effectful and declarative.\n\nBoth exist!\n\nSimon L Peyton Jones and Philip Wadler helped create Haskell, which is a beautiful, pure functional programming language.\n\nIn 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.\n\nThe 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*.\n\nBut they noticed something:\n\nIt 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:\n\n```\necho() {\nloop: a = getchar(a);\n    if (a == eof)\n        return;\n    else { putchar (a);\n        goto loop; }\n}\n```\n\nIn Haskell:\n\n``` php\necho :: IO ()\n    echo = getcIO 'bindI0' \\a ->\n    if (a == eof) then\n        doneIO\n    else putcI0 a 'seqI0'\n```\n\nDoes 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?\n\nNope! Jones and Wadler list two advantages that remain:\n\n`map`\n\nand `append`\n\n) 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.\n\nBut it *is* still imperative structure.\n\nJohn Launchbury, another creator of Haskell, wrote a paper the following year called [Lazy Imperative Programming.](https://www.researchgate.net/publication/2248360_Lazy_Imperative_Programming)\n\nIn it he says,\n\nImperative features were introduced to Glasgow Haskell for expressing input and output.\n\nCould this be plainer? Haskell has imperative features.\n\nAnd the Haskell code that uses them is imperative. Also from his paper, an \"Imperative Scan Left\":\n\n`writeVar`\n\nis an imperative statement, which is why this is an example of imperative code—written entirely with pure functions!!!\n\n[The paper](https://www.researchgate.net/publication/2248360_Lazy_Imperative_Programming) has many other references to imperative stuff, including this:\n\nIn 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.\n\n🤯\n\nThis is insanely cool.\n\nAgain, not everything in Haskell *has* to be declarative for it to still allow for incredible *execution* properties!\n\nPaul 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.\n\n(Side note: I owe my passion for programming to this paper. More on that later.)\n\nHere's an example of what that pure functional approach with an \"strongly imperative feel\" looked like:\n\n``` bash\nbox <- newEmptyMVar\n\nforkIO $ do\n  threadDelay 1000000\n  putMVar box 42\n\nresult <- takeMVar box\n```\n\n`puTMVar box 42`\n\nexpresses a command to set `box`\n\n's value to 42.\n\nIn the authors' own words,\n\nWhile 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.\n\nIt 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.\n\nBut the authors were dissatisfied with that \"imperative feel\"—so they invented functional reactive programming (FRP):\n\n``` js\nbox t0 =\n  0 `untilB`\n    predicate (time >=* t0 + 1) t0\n      -=> 42\n```\n\nJust a single declaration. Nothing here expresses a command at all. `putMVar`\n\nis 100% imperative, while this is 100% declarative and 0% imperative.\n\nSince the industry dogma in the 1990s was \"functional = declarative\", they had to call FRP\n\nmore 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.\n\nThe \"Lazy Imperative Programming\" paper was more contradictory. The author often contrasted Haskell with \"imperative languages,\" while dozens of times referring to features\n\nwithinHaskell as imperative.On the one hand, functional languages are commonly more expressive and easier to reason about than imperative languages,\n\nAfter 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.\n\nFunctional 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\n\nwithinHaskell 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.\n\nWe 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\n\n`< >`\n\ncharacters 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\n\nimperativeby the purest definition; and there are features and behaviorsdeclaredwithout scattered commands controlling them from elsewhere.\n\nThe 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.\n\nBut even the less precise definitions of imperative programming focus on **structural descriptions written in code** rather than execution details:\n\nIt'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.\n\nBut 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.\n\nFunctional programming *can* be imperative.\n\nRxJS enables fully declarative code, despite latent side-effects.\n\nRxJS actually came from that Fran paper we looked at earlier, which proposed a way to turn this kind of Concurrent Haskell:\n\n``` bash\nbox <- newEmptyMVar\n\nforkIO $ do\n  threadDelay 1000000\n  putMVar box 42\n\nresult <- takeMVar box\n```\n\ninto this:\n\n``` js\nbox t0 =\n  0 `untilB`\n    predicate (time >=* t0 + 1) t0\n      -=> 42\n```\n\nNeither 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.\n\nThe same structural move can be made in side-effectful code:\n\n``` js\nconst box = Promise.withResolvers();\n\nsetTimeout(() => {\n  box.resolve(42);\n}, 1000);\n\nconst result = await box.promise;\njs\nconst box = await lastValueFrom(\n  timer(1000).pipe(map(() => 42)),\n);\n```\n\nBoth 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.\n\nThese four code snippets alone are enough to complete our four quadrants:\n\nBut let's look at some more common declarative side-effects.\n\n\"HTML is declarative\" is often said. And it's mostly true (see above callout \"Note on \"more declarative\" and monoparadigmatic dogma\").\n\nYet the entire point of HTML is a side-effect: You being able to look at beautiful interfaces like this:\n\nIt'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:\n\n```\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" />\n<script src=\"https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js\"></script>\n<!-- ... -->\n<img src=\"https://storage.googleapis.com/blog-images-backup/1*tY3o3UFtBaMQ103en48qxA.png\" />\n<iframe src=\"https://www.youtube.com/embed/dQw4w9WgXcQ\" />\n```\n\n`Math.random`\n\n`Math.random()`\n\ntechnically produces a side effect because it relies on and mutates a hidden global internal state (the seed or PRNG algorithm pointer) inside the runtime environment to generate its next value.\n\nBut this is still totally declarative:\n\n``` js\nconst x = Math.random();\n```\n\n`Date.now()`\n\n, `crypto.randomUUID()`\n\n, `process.memoryUsage()`\n\n, etc...\n\n``` js\nconst now = Date.now();\nconst date = new Date();\nconst perfNow = performance.now();\nconst randomBytes = crypto.getRandomValues(new Uint32Array(1));\nconst uuid = crypto.randomUUID();\nconst hrtime = process.hrtime();\nconst uptime = process.uptime();\nconst memoryUsage = process.memoryUsage();\nconst freeMemory = os.freemem();\nconst battery = await navigator.getBattery();\nconst windowWidth = window.innerWidth;\nconst visibility = document.visibilityState;\nconst online = navigator.onLine;\n```\n\nAll of these are completely declarative. They are declarations of final results, and no subsequent step-by-step (or any) imperative code is involved.\n\nBut they are not pure functions. They don't have side effects, but they have side causes, so they still are not functional. So while they aren't examples of declarative code that causes side effects, they are examples of declarative code that break rules of functional programming, showing that these are independent dimensions.\n\n``` js\nconst fib = (n: number) => n < 2 ? n : fib(n - 1) + fib(n - 2);\nconst result = fib(50);\n```\n\nEverything here looks pure as the driven snow, and it's definitely declarative.\n\nNo side effects at all, right?\n\nIn fact, it triggers 40,730,022,147 calls, which means that its side effects are so severe that I really don't recommend pasting that into dev tools and running it. You think jQuery has interesting side effects by reaching into the DOM and changing the behavior of random things, but this will change the behavior of everything on the page. Namely, it eliminates it because it locks up the CPU.\n\n```\nnew Array(2 ** 30).fill(0);\n```\n\nNo side effects here, right? It's declarative, at least.\n\nHowever, this might get compiled into assembly code that looks like this:\n\n```\nmov     rdi, 8589934592\ncall    malloc\n\nmov     rcx, 1073741824\nmov     rdi, rax\nxor     eax, eax\nrep stosq\n```\n\nThere is a huge side-effect in this code: `call malloc`\n\nrequests `8589934592`\n\n= (2 ** 30) × 8 bytes = 1,073,741,824 × 8 bytes ≈ **8 GiB** of memory.\n\nThere may be guardrails against creating arrays this big. I didn't feel like testing it. But there is a variation that may not be prevented:\n\n``` js\nArray.from({ length: 1000 }, () => new Array(1_000_000).fill(0));\n```\n\nThis time each inner array has only 1 million elements, but there are 1000 of them, so 1,000,000,000 total elements.\n\nHere are some possible side-effects:\n\nAnd yet, it's still declarative.\n\nDeclarative and functional are not synonyms. Declarative code is about *how a computation is structured*: describing a result rather than spelling out a sequence of commands to get there.\n\nFunctional purity is about *what that computation depends on and affects*. A declaration can trigger network requests, consume CPU and memory, observe clocks and runtime state, or depend on hidden mutable state without becoming imperative.\n\nThese are separate dimensions.\n\nThe point of separating these dimensions is not to win a pointless terminology argument. It is to make better engineering decisions while AI is applying incredible pressure on our architectural patterns.\n\nIf we collapse **functional** into **declarative**, we lose the ability to talk precisely about what code is doing. Pure code can still be structured imperatively. Declarative code can still observe mutable state, trigger I/O, allocate absurd amounts of memory, or otherwise affect the world.\n\nStructure and purity are independent properties of code that sometimes align, but *DO NOT HAVE TO*!\n\nIf the problem is hidden state, surprising effects, or difficulty reasoning about dependencies, functional programming can help with its explicit inputs, immutable values, pure transformations, effect isolation. And although I love functional programming, in my experience, most apps genuinely will never see significant consequences for not being purely functional.\n\nHowever, most apps I have seen have suffered severely from being imperative: It always scatters control, with commands reaching across a system (spaghetti code), or behavior that can only be understood by mentally executing twenty steps across multiple contexts. Declarative code fixes this. Functional programming does not.\n\nI am sick of saying I like declarative code and being told to juST UsE ELm or HASkElL.\n\nPeople can't even understand the problem because they don't have the words to discuss it properly.\n\nNow throw AI into this cross-talking mix of contradictions, and this is what we can expect:\n\nThis looks delicious, but can we please try to avoid it anyway?\n\nLet's fix our vocabulary so we can learn real things about imperative code and [steer humans and AI away from it.](https://dev.to/playfulprogramming/5-reasons-to-avoid-imperative-code-e09)", "url": "https://wpnews.pro/news/functional-languages-are-heavily-imperative", "canonical_source": "https://dev.to/playfulprogramming/functional-languages-are-heavily-imperative-4c1", "published_at": "2026-08-17 07:39:18+00:00", "updated_at": "2026-08-17 08:12:23.256902+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Haskell", "Simon L Peyton Jones", "Philip Wadler", "John Launchbury", "Glasgow Haskell"], "alternates": {"html": "https://wpnews.pro/news/functional-languages-are-heavily-imperative", "markdown": "https://wpnews.pro/news/functional-languages-are-heavily-imperative.md", "text": "https://wpnews.pro/news/functional-languages-are-heavily-imperative.txt", "jsonld": "https://wpnews.pro/news/functional-languages-are-heavily-imperative.jsonld"}}