Bad Engineering Properties Of Functional Languages

LucaCardelli wrote a famous paper entitled BadEngineeringPropertiesOfOoLanguages, which discusses some (perceived) shortcomings of ObjectOriented programming languages. Recently, on ManifestTypingConsideredGood, Cardelli was paraphrased by a well-known Wiki user who suggested the existence of BadEngineeringPropertiesOfFunctionalLanguages.

ManifestTypingConsideredGood advances one argument in that direction; in that in many instances, explicit type annotations (as opposed to LatentTyping, whether via dynamic type-checking a la SmalltalkLanguage, or TypeInference as found in HaskellLanguage and MlLanguage) may be a GoodThing. I won't argue with this, though I will note that the latter languages at least do allow explicit type annotations to be inserted anywhere in the code. You can write ML with every label declaration, object component, etc. containing a type declaration if you want to.

This page is for other arguments against FP to be made, given that it's implied they exist. As a suggestion, arguments focusing on the properties of languages themselves should be kept separate from arguments concerning the maturity of toolsets or arguments concerning the acceptability of a language to the PointyHairedBoss.

I'd suggest that the most grievous property of functional languages is that they don't allow one to perform computation. Don't get me wrong - they allow you to describe transformations over values, such that a real computational system that understands the language (the interpreter) may perform them. They even allow you to describe arbitrary computations (with monads). But a pure functional system cannot actually perform computations that you describe. Even Haskell must embed a computation description with an IOMonad into a non-functional main program.

I can vaguely see what this is pointing at, but you're using words imprecisely - every program, regardless of the language, is trivially a description of a computation, and the computation is the act of processing that description. Turning a description into a series of actions might have some differences between functional and imperative languages, but this is in no way some clear-cut distinction. This is because computation, itself, is a process - a 'doing', involving the sending and receiving of messages (e.g. between the CPU and memory, human->keyboard & mouse//CPU->VGA Adaptor->monitor->human, etc.)

Functional languages are excellent at describing descriptions of computations, since they are excellent at describing values. However, if one were to speak a functional language, one could never give an actual command - an imperative; one cannot describe actions to be performed. A functional language lacks, by definition, the three fundamental computational primitives (send - deliver a message, receive - accept a message, and value representation - representing a value in some sort of external, addressable, physical state or wave-form for delivery or receipt). While one can describe values that describe computations, the most one can ever do is describe calculations that emulate the execution of those computations...

... unless you decide to go Imperative.

IOMonads, in Haskell, are values that describe computations - sending and receiving. While, for the most part, Haskell is functional, the special meaning given to an IOMonad when used in the context of a Main function turns it into an imperative: 'Do this IOMonad.' This allows the basic human -> keyboard -> CPU -> monitor -> human signal system.

Of course, adding those sorts of primitives means you no longer have a pure functional language or system. The special meaning given to 'Main' by Haskell is not fundamentally different from the special meaning given to any particular description of computation in a widely imperative language. Common protestations from those in the Haskell community don't change this. Assuming a keyboard-video-human system is functional is the same as assuming that humans are functional and that the same outputs to a human would necessarily result in the same inputs to the keyboard. (As an aside, it's quite rude to turn humans into a primary storage device!)

Pure functional languages are largely useless.

... But starting from a PureFunctionalLanguage and adding the minimal necessary set of computational primitives (send, receive, cells, and value representation) is probably the way of the LanguageOfTheFuture - one may produce a true ComputationalLanguage rather than a crippled half-form (a PureFunctionalLanguage like Haskell or PureImperativeLanguage? like Fortran). Ready access to functional primitives is incredibly valuable. The content of any meaningful message or cell is always a value held by a ValueRepresentation?, so a language good at describing values is good for calculating what should be placed in cells or communicated to other processes. At the moment, only PureFunctionalLanguages? fill that role; those that mix the two without care cannot be sure they will deliver a value. It's best if one can send '~(f 1 2 3)' as a message, and have it understood meaningfully as 'the result of lazily applying 1 2 3 onto some <commonly known function f>', as this allows arbitrary abstraction... a true language with sentences. ImpureFunctionalLanguages? that mix IO with functions, like Scheme or OCaml, are broken for this purpose because 'function' isn't synonymous with 'value'.

What is really needed is a ComputationalLanguage. This needs a pure functional language subset, with which it can create arbitrary values, including those values that describe computations (things to do) and computational services (objects or actors that may receive a message and respond). To make such descriptions usable, a ComputationalLanguage must describe computations in terms of a common set of computational primitives that the underlying system knows how to 'do'. Finally, it needs a minimum of one primitive operator that, given a description of a computation, essentially, says 'do it!'

Pure functional languages are fundamentally crippled when it comes to Computation. Of course, pure OO languages, where such things as integers are passed around as 'objects', are just as bad - the content of a message is NOT the object by which it is delivered. (See MutableState).

You say that "the underlying interpreter will only ever be emulating a computational environment". As far as I can tell, that statement is devoid of any real meaning. First, functional languages don't require an interpreter any more than imperative languages. Both typically require an operating system of some sort, and a CPU of some sort. The OS and CPU actively consipire to "emulate a computational environment". So, by your argument, imperative languages must have the same flaw that you claim for functional languages (whatever that flaw may be; you never really state it). -- MichaelSparks

Perhaps I'm not making myself entirely clear. The CPU and memory systems (including the registers) on a hardware unit constitute a physical environment in which messages are delivered and received with certain protocols. Messages are delivered along a hardware bus via messages represented (physically) by signals in the form of electrical impulses (in the typical electronic computer, anyway). Further, memory (by definition) provides some sort of cell by which to ground messages, and an addressing mechanism for retrieval. Thus a CPU, by itself, fulfills all three basic requirements for a computational environment above and beyond the functional requirements: the ability to send, receive, and capture messages. No emulation is necessary. To request the contents of a particular memory cell, the CPU must first deliver a message (e.g. get:(0x..addr..)) to the memory subsystem, then must await a reply. The reply will be another message (e.g. contents:(0x..addr..,0x..value..)). Use of transistors or whatnot as the physical medium for the storage is arbitrary.

Computation is a process. To perform any computation will involve all three of those steps (send,receive,capture), usually in copious amounts for any non-trivial task. It doesn't matter whether it is a human or a computer performing the task. However, there is also the two functional requirements: values, and transformations on values - the ability to take one or more input values (e.g. operators and numbers) and produce an equivalent output value. I.e. math. '3 + 5' = '8', so '8' => '3 + 5' and '3 + 5' => '8' are both valid transformations (though the value we're most regularly interested in reduces the size of the represented value). Of course, there is a physical plane where these values must be both represented and transformed.... a CPU does so through physical manipulations (of transistors and electrical impulses), while a brain does so through its own slightly wetter versions.

Here's the issue: A pure functional language can only ever describe values and transformations. A functional language cannot directly describe the computational process because it intentionally excludes all the necessary components (send/receive/capture). Thus every pure functional language needs an interpreter - something or someone that can look at it and produce the steps necessary to transform the values as described in the language. Whether said interpreter is a human, a procedure implemented in a computer, or a compiler isn't relevant. It's still necessary.

The values described by a pure functional language, quite trivially, can describe computations. E.g. one could store the meaningful contents of an ASM-language file in an addressed list. It's even legal for a functional system to describe all the transformations and events a compiler would perform, given such a list, through use of monads and such. However, it is illegal for an interpreter of the pure functional language to actually act on these descriptions - e.g. to compile and said ASM-code directly upon the CPU. It's legal to describe, but illegal to act. If acting ever became part of the language definition, then the language is no longer purely functional. WRGT Haskell, one may describe IOMonads and the transformations performed all they wish, but once interpreters start taking liberties when they see that a main exists and decides to turn IOMonads into real IO, the language isn't functional anymore. I suppose if you weren't expecting that to happen, you could make a counter-case, but the fact is that once you start describing things in your language with the expectation that certain actions will be performed upon it, it's no less imperative than any other turing-complete imperative language. (And by reflection, no turing-complete, imperative language is any less functional than it.)

Now, here's the issue you claim I'm circumlocuting: If you have a functional language, you can't do anything. The moment you change things so you can do something (e.g. attach IOMonads to Main), you no longer have a functional language. It's rather fundamental. Functional languages don't allow you to perform computations. And being useless, to me, seems to qualify as a BadEngineeringPropertyOfFunctionalLanguages?.

Imperative languages skirt this problem by simply allowing one to describe computations. If the language is fully specified (like Assembler), then these do not require interpreters in the sense above - the described actions would speak for themselves. However, such a language might need to be transformed a few times prior to use.

Isn't that a roundabout way of saying pure functional languages aren't practical? There are numerous academic illustrations that are similarly impractical, but this doesn't deprecate the value of products derived from or inspired by them. What does it say about non-pure functional languages? -- DaveVoorhis I'm not quite sure why you think that the IO monad leads to impurity. The best guess I have is that you are confusing determinism with functional purity. Is that it? Also, what if I don't use the IO monad and declare my program as "main :: String -> String"? Here, I can take input from stdin and give output to stdout, but I'm not using monads. Would you consider that program to be impure as well? -- MichaelSparks

OK, I think I am starting to understand what you mean. Let me run another hypothetical by you. Suppose I define my program as a function of type "Main = String -> (String, Main)". When I give the program to a user, I instruct him that he should run a function called "main" with his input string, and that it will return an output string and a new function, and that if he is so inclined, he may then run the new function in the same manner. Would you consider this program to be pure? -- MichaelSparks

In this system, to use your terminology, the user is playing the part of the "interpreter". So it is the interpreter that is impure, not the program being interpreted. Do you agree? -- MichaelSparks

The function I ask the user to run is called "main", not "Main". "Main" is the type of "main". Your latest reply is pretty confusing to me. I take it that you are trying to redefine the symbol "Main" to refer to the instructions I gave the user. I have no idea why you would do that instead of just using a new symbol. But I should have expected as much, since this whole debate, as DaveVoorhis pointed out, is almost entirely about catering to your peculiar definitions. I'd like to get back to the part of the discussion that isn't about definitions. Can you restate your last message in simpler language so that I can understand it? -- MichaelSparks

EwDijkstra once said: Computing science is no more about computers than astronomy is about telescopes. Functional languages make a point of not being about computers.

When a mathematician writes "let x = 1" on the chalk board, this is assigning a value to variable X (or constant). This is a side effect and there is state, isn't there?

Math is a lot like imperative programming but math doesn't make much distinction between variables and constants. Lots of people write x=1 on the chalk board and forget about saying "let x=1". Notice the "let" in front. It could be argued that X is a constant and doesn't need assignment. I think this needs to be cleared up and math needs to be merged with programming, as I find programming a far more powerful "math" than regular math. Math also deals with numbers, but doesn't really care much about string concatenation and text (parsing, substrings, etc.).

How do you set x to a value of 1 without having state?

I am very skeptical about the whole purity arguments of functional programming, as I think math can also be done using state. Does not a calculator that you use, store state? Can you not store things in your calculator? I consider functional purity to be sort of MentalMasturbation. I could change my mind in the future, but it seems odd to massage everything into a final result rather than do it in sections with state like imperative programming. If you were going to clean the windows on a building, would you break the job up into pieces and clean each window (foreach window do Clean();) or would you think about it functionally? I think imperative is more natural (foreach person GiveOneNapkin?(); at the dinner table).

Having just re-read this whole debate, there's something I'm not clear on: If functional languages require imperative programming - i.e., must be "impure" - in order to meet real-world requirements, why is this a problem? Doesn't the expressiveness of the pure functional aspects, used in conjunction with imperative facilities, overcome the limitations of using purely imperative languages to meet the same requirements? If so, this is hardly "bad engineering," as used in the title of this page. At worst, it is perhaps inaccurate to call such languages "functional," but it in no way deprecates the value of languages generally (though informally) regarded as functional. This page strikes me as a problem of definitions, or of theoretical ideals getting in the way of practical issues, or even of a preference for imperative programming over functional programming; it is not identifying a real problem with practical functional languages. If there is a problem using theoretical "pure" functional languages to meet "impure" real-world requirements, then so what? -- DaveVoorhis Thank you. I believe you've confirmed my hypothesis that this page is a problem of definitions rather than of languages themselves. I.e., a LaynesLaw debate. Essentially, you are objecting to the use of the term "functional" - which appears to imply "pure functional" to you - to refer to languages that are (like most languages!) hybrids. Most programmers, to my knowledge, simply use the term "functional" to refer to those hybrid languages that are significantly inspired by - and typically implement - first class functions, higher order functions, and/or LambdaCalculus.

Regarding your claim that my "so-called 'practical functional languages' aren't functional..." I would actually prefer to simply call them languages with a given set of features, and leave the buzzword labels like "functional" in the marketing department where they belong, because they too often lead to debates over definition like this one. If you have a different, and arguably better way of mixing functional and imperative approaches, then you may be onto something. I'm not sure introducing such ideas benefits from deprecating the existing ones, however, at least not without showing how your way is better. -- DaveVoorhis

Ah good, a new buzzword for the marketing department! ;-) Of course, the marketing department would like to know what ModelOfComputation a ComputationalLanguage is based on. Imperative programming is ultimately based on the TuringMachine and VonNeumannArchitecture, and functional programming is based on LambdaCalculus. What model is the theoretical basis for a ComputationalLanguage? From a cursory reading of it, I sense aspects of FlowBasedProgramming and ActorsModel. -- DV

I think physics would be the best model. Why arbitrarily limit yourself? ActorsModel is based off physics, too, which is why any ComputationalLanguage will share considerable aspects with it. If some core aspect of Computation is not fundamentally blocked by physics, then one should not arbitrarily limit it.

Physics and information theory are fields of study, not models. -- DV

In that case, no model at all should be used. One only needs to implement the necessary primitives for general computation (that occurs in a physical environment) in a convenient manner. Base it off the physics of computation, and you won't run into problems until physics changes. A ComputationalLanguage should be designed to work just as easily in the ChineseRoom as it does on a VonNeumannArchitecture or TuringMachine.

That being so, a ComputationalLanguage is a bag of arbitrary language features, i.e., a MultiParadigmLanguage. Is there a characteristic that makes it distinct from other MultiParadigmLanguages? -- DV

Paradigms such as OO, Functional, etc., attempt to explain computation from a certain viewpoint; in doing so, they create computational primitives that aren't... well... primitive, at least in the sense of computation and physics. Through libraries, a ComputationalLanguage can be used to implement any computational paradigm that is physically viable. In a sense, that makes it a MultiParadigmLanguage, since one can expect the standard libraries to come equipped for several such paradigms. However, a MultiParadigmLanguage does not necessarily qualify as a ComputationalLanguage. It would not qualify, for example, if it did not implement every single one of those multiple paradigms with the same set of underlying language primitives. It'd also be disqualified as such if either the Typechecker or Lexer/Parser needs different 'modes' depending on the paradigm you are currently using, as then it isn't really a unified language. Finally, it'd be disqualified as such if there are any paradigms that are physically feasible that it cannot implement.

You've provided a basis for, perhaps, distinguishing a flexible MultiParadigmLanguage from an inflexible MultiParadigmLanguage, but I still don't see what characteristics make a ComputationalLanguage distinct from a MultiParadigmLanguage. Unless it is your intent is that a ComputationalLanguage refer to a MultiParadigmLanguage in which every (!) paradigm can be implemented via the language primitives? If that is so, are C or C++ examples of ComputationalLanguages, since these are effectively generalised assembly languages sufficiently powerful, and with adequate primitives, to implement any language in any paradigm? And if that is true, then would not almost any language qualify as a ComputationalLanguage, since any language can be implemented in any other general purpose language, subject to performance constraints and programmer effort? -- DV

It'd be somewhat ridiculous to claim any modern, regularly used programming language is a ComputationalLanguage, since they don't use ComputationalPrimitives.

After the arm-waving about ways of looking at things, mixing values and behaviours, signals and state, and so on - all of which, I'm afraid, conveys little meaning without illustrative examples - in the comment I've quoted above it appears we have the beginnings of a theoretical (hopefully) and distinguishing basis for a ComputationalLanguage. If I understand correctly, then, a ComputationalLanguage is a language built from ComputationalPrimitives. Yes? If so: In answering this, by the way, I would hope for presentation of, and/or reference to, a formalism a bit more rigorous than passing mention of philosophy, physics, or Greek and Chinese guys. -- DaveVoorhis

I took the freedom to annotate your requirements for a ComputationalLanguage by the corresponding implementation by VhdlLanguage. I have to point out that I like your systematic treatment of the matter, but fear that you developed a PrivateLanguage for something that is quite well-known - even if not in the usual programming curiculum. But maybe you know VHDL already and just propose an integrated treatment of it and "normal" programming languages. -- .gz

Computation is the process of calculation. As such, it is a series of actions. Therefore, you need to support at least a set of action primitives. It is provable that a sufficient set of action primitives are to send and receive messages. {Note: some damn WikiPuppy injected all the VHDL comments. This section wasn't even about VHDL.}

I'm trying very hard not to be impolite, but could you please try to understand the technology you are ranting against? First off, a thing like 'getChar' is not an "IOMonad". It's a value of type 'IO Char', commonly called an 'IO action'. The monad is IO. This may seem like nitpicking, but actually statements like these show clearly that you lack understanding.

Secondly, a CPU is a purely functional device. It has type '[Response] -> [Request]'. You explained it yourself: CPUs communicate by way of messages. They receive a stream of responses and the produce a stream of requests, interleaving them of course. This is exactly the way IO worked in Haskell 1.2 and before. The type of main was exactly this. Given this setting, I can implement the IO monad in a purely functional way, using nothing but Haskell 1.2. There goes your argument about embedding another language.

Even considering a more practical implementation (more practical on a common register machine of course), the IO monad still has clean denotational semantics. Sure, stateful computation gets ugly really fast, but that's just life. That's why you separate pure computation and stateful computation. That causes far less headache than giving up from the outset and enduring an imperative language even if not needed.

Strictly, I never said anything about 'getChar' being an IOMonad. However, you're absolutely correct that I should have been more careful to separate the use of an IOMonad (description of IO action, accepting an IOMonad and returning a character and a world) from the nature of the IOMonad (which describes the existence of input and output message ports as part of the world, making them accessible for IO actions). I thank you for pointing this failure out, and I'll take more care in the future. However, a failure to utilize your precise terminology does not indicate a lack of understanding; it is merely a failure to communicate the understanding I do possess.

In response to another of your remarks: I'm not "ranting against" the use of IOMonads as a technology. On the contrary, I think they're a fine idea. But it's important to recognize that when attaching these sorts of monads to 'main' with the sort of special meaning Haskell imparts, you no longer have a (purely) functional language. You have an imperative one. Just as 'getChar' is a word in a language that is interpreted as a command to go fetch a character from a commonly known input stream, so too is attaching an IOMonad to 'main' interpreted as a command to connect certain real IO to the functions so the internal 'IO actions' can, themselves, become just as imperative. The nature of imperative vs purely descriptive sentences, in languages as a whole, exists only in how one expects for them to be interpreted. After all, every sentence in any language is merely a value - a string of words, held together with a shared syntax. That includes the use of 'getChar'. Yet I'm not saying it is bad that Haskell gives special meaning to an action description (an IOMonad and associated IO actions) attached to 'main'. Overall, that's a good idea; it overcomes one of the rather BadEngineeringPropertiesOfFunctionalLanguages. But, in doing so, the language isn't truly functional anymore (at least not in the 'pure' sense)... and so it doesn't qualify as a contrary example to the BadEngineeringPropertiesOfFunctionalLanguages that I've presented.

Finally, you are quite mistaken in your statement that "a CPU is a purely functional device. It has type '[Response] -> [Request]'". A CPU is a physical device, in the real world. Inputs to a CPU are signals that carry messages, and energy; outputs are signals that carry messages after some delta-time, and heat. The transformations that determine the output signals occur by physical means, and involve physical alteration of the real world. CPUs are fundamental computational units... and, as such, are just as far from 'functional' (in the mathematics sense) as are you and I. You need to add quite a lot to that little '[Response]->[Request]' description before you even get remotely close to a valid abstraction of a CPU. But even if you did, you're still mistaken about "receiv[ing] a stream of responses and the produc[ing] a stream of requests" being even remotely functional. Any boxed process that is receiving messages from and sending messages to external processes (services, etc.) is interacting with the world around it in a very real manner. That is not functional. Period. The closest you can say is that the computational process is stateless and timeless, such that the response you receive to a message is independent of all other messages the process has received. Functions cannot send messages because messages are inherently founded in time and space, signal and medium, while functions may only abstract and describe values. The moment messages are involved, you don't have functional. That's fundamental.

Wrgt: "separate pure computation and stateful computation" - you should say separate conceptual 'calculation' from stateful or message-oriented 'computation'. There is no such thing as computation that isn't, in some way, involving signal and state. There is no "pure computation" in that sense.
Reading some of the above descriptions, in comparing functional or imperative software to CPUs in their effort to dissociate computation from communication, I just had to chuckle.

See, computers are composed entirely of these things we call gates, and there are a handful of them. AND, NAND, OR, NOR, XOR, XNOR, pass-thru (aka "buffer"), and inverter. All of these gates are pure functional in nature; given the same set of input values, they will always produce the same output value(s). None of these gates are stateful.

Yet, using two NAND or NOR gates, we may implement state: the R/S flip-flop. This flip flop is the basic unit of static RAM -- it literally is a 1-bit SRAM. Other gates may be added for things like output enables, write strobes, et. al., but again, we're adding pure functional elements to arrive at a stateful system.

Since it's established that a computer both computes and communicates its values to the external world, and we see that those components responsible for directing the communication are built entirely from pure functional elements, we therefore have no choice but to conclude that being pure functional is not a bad engineering property -- on the contrary, it is the very feature that enables us to argue this on wiki in the first place.

Why don't you move this page to PurelyFunctionalLanguagesDontExist?, nothing here is about BadEngineeringPropertiesOfFunctionalLanguages, unless you claim 1. Functional is equivalent to PurelyFunctional, 2. The convention of "'running the program' means evaluating main and executing the returned side effects" makes a language impure (and thus, by 1, not Functional), and 3. Nonexistence is a BadEngineeringProperty?.

Why don't you?


View edit of November 9, 2014 or FindPage with title or text search