Imagine you’ve implemented a large program in a purely functional way. All the data is properly threaded in and out of functions, and there are no truly destructive updates to speak of. Now pick the two lowest-level and most isolated functions in the entire codebase. They’re used all over the place, but are never called from the same modules. Now make these dependent on each other: function A behaves differently depending on the number of times function B has been called and vice-versa.
In C, this is easy! It can be done quickly and cleanly by adding some global variables. In purely functional code, this is somewhere between a major rearchitecting of the data flow and hopeless.
I am almost certain a Haskellite will say something about monads or arrows, but I don't know what.
EDIT: I guess another criticism might be "when would you possibly need this?". It smells suspiciously like a symptom of bad design -- these two totally independent, low-level calculations are now dependent on each other. A possible use case might be a tweak to an artificial physics, as in a simulation or game.
This criticism echoes what E. W. Dijkstra said before,
As long as programs are regarded as linear strings of basic symbols of a programming language and accordingly, program modification is treated as text manipulation on that level, then each program modification must be understood in the universe of all programs (right or wrong!) that can be written in that programming language. No wonder that program modification is then a most risky operation! The basic symbol is too small and meaningless a unit in terms of which to describe this.
Using global variables in C is still rearchitecting the data flow. It's just a really obvious way to do it.
You basically have 4 things here: functions A and B and variables a and b. Calling A increases a, calling B increases b. Function A depends on a, b. Function B depends on a, b.
In FP, you would probably pass those variables around. In C or some other language you could store them as global variables or pass them around. Global variables are a shortcut for passing them around. Just as you don't pass around all of the assembly code of the functions you want to use to the next function, you don't need to pass all variables around all the time.
To answer your edit: these two totally independent, low-level calculations are now dependent on each other.
This is normal, just look at any example of co-routines. It's like a two-player game where one player goes first but it doesn't matter which.
>In FP, you would probably pass those variables around.
The point is that you might have to rewrite every part of the program in order to "pass around" those variables, where as in an impure language with global variables, this sort of major rearchitecting is not going to be necessary.
The pure vs. impure stuff is a red herring. It's meant to throw you off the scientific trail. If there are merits to the FP approach, as there are, then one or two drawbacks in a few of the languages aren't a problem. If there are multiple problems with the FP approach, then we discard it and toss out the languages that use it or modify them to only use the useful subset of that approach.
With what little information we have in this example, it would be best to modify the language so that it includes those two low-level functions in its specifications and make those global variables they require.
Modifying the language spec...is that pure or impure I wonder?
The reverse is true. Just modify the language. I don't see why people are insisting on this "purity" in approaches. It's obviously leading to difficulties.
There are inherently some changes that are extremely hard to make to a well-constructed functional program, and there are equally some changes that are hard to make to a well-constructed procedural program.
I would hypothesize there is no language paradigm in existence that provides optimal efficiency for implementing all possible types of architecture changes.
I think what you're saying here is close to what the OP is arguing. The title obscures this a little bit, but it's clear from the second paragraph that he's arguing against purely functional programming.
I think the first part of your hypothesis is correct, but not quite the second. Most imperative languages support deep recursion and reentrancy, which means that they're a proper superset of (most of) functional languages. You may need to create a "closure" by hand and pass it around, but there's nothing that's fundamentally impossible. The example in the article about adding a side effect and cross-dependency to a deep function really isn't possible in functional terms without changing the design (i.e. adding the state to the function's interface).
Your hypothesis would be correct because languages only scratch the surface of thought. The underlying computations invoked are the important thing and you must understand them before you implement them, no matter which language you're using. Obviously some languages are easier to work with and give you better abstractions to work with making it quicker to write the code but you still have to understand what the heck it is you're really trying to do.
No one disputes that sometimes imperative styles of programming are clearer, or more efficient. Efficient functional programming is a new topic that is growing in importance as it becomes clear that the future is rooted in highly parallel programming.
But these specific complaints mounted at Erlang don't really ring true to me. For one, it's idiomatic to pass state along in functions, and it's not difficult at all to write branching logic based on a variety of cases in state. Now, if you have hundreds of fields in your state you will surely find Erlang to be more awkward than other functional languages, because you can't do things like pattern match and run guards into dictionary structures. But this isn't a failure of "functional programming", it's probably more of the nature of Erlang. Erlang just isn't optimal for that kind of work.
It seems to me like the author's complaint is more "Erlang wasn't the best for my projects and programming style" rather than "Functional programming has failed." Erlang is written by engineers who understood their target domain very well and wrote a very elegant system for wrangling it. Game development wasn't exactly on that list of things Erlang was meant to do well. :)
You make a key point that is often forgotten when people are arguing about programming languages. As part of the process of designing a program, you should pick which language (or programming paradigm) works best to solve that problem. A lot of intellectual time is spent arguing about preferences, and using one language to program a solution that would be much easier in another.
I do a lot of statistical work, and sometimes I use python, sometimes I use C or Fortran, sometimes I use R or PLT Scheme if I want to have some fun. I would agree that I am not a true expert in any of those languages, whatever that may mean, but I can solve the wide range of computational problems that I need to address quickly and efficiently in one of those languages.
This might just come from that fact that an undergrad class drilled into me a healthy disregard for the idiosyncrasies of syntax, libraries and idioms, and instead instilled in me the idea that semantics is the only serious issue for a student of programming languages.
In Haskell you might do the same thing as C. By recasting the logic where A and B are called to include some read/write state you can get "globals" just the same and, yeah, this can be done with Monads (State or even IORef).
The question becomes, though, why are A and B suddenly interlinked? Why was this context not already in place? Is there not a better structure to this data that would allow a minimization of stateful computations? Does this sudden A<->B dependence actually suggest a radically different shape of the code?
A well-written functional program should make these refactorings "easy" by having many useful and well-defined interchangeable pieces, and often it turns out that complex Haskell programs must refactor until the best dataflow structure is found (Theory of Patches for Darcs or the Zipper in xmonad).
Which in the worst case will involve lifting the type of pretty much every function in the program into a monad. Which is not going to be necessary in C.
> The question becomes, though, why are A and B suddenly interlinked? Why was this context not already in place?
Here's an example, roughly taken from a project I did not too long ago (in an imperative language, not a functional language). Suppose you have an e-commerce system. One day, the business decides to start giving out coupons, say for 10% off. Any order can have a 10% off coupon applied to it. All is fine and good, orders now have an optional coupon attribute, you compute order totals in an obvious way, coupons are nicely orthogonal to the rest of the system.
Then some time later, we decide to add free shipping coupons. But, there's a wrinkle: they only apply to ground shipping. Now, you can only add a free shipping coupon to an order with ground shipping; and, also, if the user changes their shipping method, you have to go look to see if they have a discount which now must be removed from the order. Now, two previously independent aspects of order placement and processing are coupled together.
Because this was written in an imperative language and backed with a stateful database store, it was mostly trivial to make the changes required. This scenario is not actually as bad as the situation the OP described, but my feeling is that it would be more difficult to have dealt with in a pure functional environment.
(BTW, this is actually a fairly mild example of the sort of complicated, non-orthogonal rules which come up in e-commerce systems. I picked it not because it was the hardest to translate to a functional paradigm, but because it was easy to explain in a couple paragraphs.)
Okay, good example. At the same time, though, trying to see the problem through a functional lens I'm stuck wondering why there isn't already some stateful context in place. You're linking coupons with the order data and unless you've been just threading that through functions --- which nobody wants to do --- I'd feel like that there would be more structure.
For some reason I'm stuck on the idea of using heterogenous lists to store "things that can affect the order". You apply them in order in a stateful context and produce a finalized order or an error. To add coupons you just make an instance that lets coupons be a "thing that affects the order" and stick it in the list. The code is decoupled, order processing is modular, and you're using a data structure which fits the problem well.
I'm obviously using some 20/20 hindsight, but I think that frequently the initial headaches of refactoring by type turn into insights to how the problem is forcing you to use the right tools.
Do I need to save their return values to avoid calling them too many times? Am I reusing a stale value where I should have called it again, and how should I know that without calling too many times? Do these hidden states need to be visible across multiple cores, and if so how do I do that without imposing a memory fence and a pipeline stall on every call? How does every unit test rewind all these hidden states back to their initial values, so they don't pollute each others' results? This change should be large and even painful, because you're inherently making the program harder to reason about and probably breaking a lot of code without knowing it.
Yikes! This might be an ill-defined problem in some cases -- optimization based on referential transparency in the original functions means that you may not really know how many times these functions were called. Either you have that problem, or the functions were already stateful.
If you've designed something well, then all changes can be easily accommodated.
But to "design something well" requires omniscience, because it depends on understanding the problem, and in what ways that problem will likely change in future. It's a question of fact about the world, not an intellectual, mathematical or computational truth. When you are very familiar with a problem, you acquire this domain knowledge, much of it informally and unconsciously, and then you can design well... or, well enough for practical purposes.
Along the way, in the process of acquiring this domain knowledge, you will make mistakes, and you will need to change things. Fred Brooks: Build one to throw away. You will anyway. Oh, and then you get the second system effect, when you try to correct all the mistakes of the first disaster.
Unfortunately (or fortunately, if you like learning), most of us move on to new projects and new domains so quickly, that we never acquire that level of mastery of a problem domain. How many people have written the same kind of application three times from scratch?
> If you've designed something well, then all changes can be easily accommodated.
I disagree with that. If you can easily accomodate any changes it means you probably over-designed and over-generalized and made your solution too complicated.
> But to "design something well" requires omniscience, because it depends on understanding the problem, and in what ways that problem will likely change in future.
Yes, that is the problem. A lot of good programmers are prone to over-generalizing, even for code that should be simple and straight-forward. Sometimes it pays off, but not always.
> Fred Brooks: Build one to throw away. You will anyway.
I tend to approach programming as experimentation. "Let's build something and try it out" kind of approach. Then "if it works, we'll enhance it, otherwise we'll throw it away and try something else". It seems wasteful but with a language like Python it is easy to prototype things out.
If there is information that needs to be accessible across several not-otherwise-very-interdependent modules of a program, then you need to have something with essentially all the properties of a global variable. That doesn't' necessarily mean that you need a single identifier with global scope, but it does mean that you need something with essentially the same properties.
My initial reaction was also "why would you". Take the "two most isolated functions in the entire codebase" and make them dependent on each other? OK, assuming there is a good reason (not given in the example) to do this, I'd say you achieve this by passing whatever bit of state data you need to drive the behavior in the various calls to functions A and B. No need for global variables to achieve this, nor is it really any easier or cleaner to use them.
>I'd say you achieve this by passing whatever bit of state data you need to drive the behavior in the various calls to functions A and B.
Obviously you can do that. The author mentions this possibility. But his point is that passing the state around may involve adding extra parameters to a very large number of functions, which is much more difficult than just using a global variable.
It seems odd to me to say that faking global variables by clumsy manual state threading is cleaner than just using global variables. It is more error-prone, more verbose, and less indicative of program structure and programmer intent.
Using global variables is far easier in this case and may entail changing a whopping three lines of code: one for the declaration of the variable and one each in the two functions using the new variable. The argument passing variant on the other hand requires changing almost every single function call in the whole program. In this sense, imperative programming is strictly more expressive than purely functional programming (in the sense of the word that IIRCgoes back to Felleisen: a paradigm is more expressive if expressing a construct that is localized in this paradigm requires a global program transform in another paradigm).
Of course this doesn't mean that a program will be easier to read after going the easier to write way too many times...
Well could you not write new A and B functions that invoke the original functions with state parameters added while also providing the state-handling mechanisms. Then all other existing calls remain unchanged. This really just introduces a namespace for what would otherwise be a "global" but it keeps the real "global" namespace unpolluted.
Imagine you’ve implemented a large program in a purely functional way. All the data is properly threaded in and out of functions, and there are no truly destructive updates to speak of. Now pick the two lowest-level and most isolated functions in the entire codebase. They’re used all over the place, but are never called from the same modules. Now make these dependent on each other: function A behaves differently depending on the number of times function B has been called and vice-versa.
In C, this is easy! It can be done quickly and cleanly by adding some global variables. In purely functional code, this is somewhere between a major rearchitecting of the data flow and hopeless.
I am almost certain a Haskellite will say something about monads or arrows, but I don't know what.
EDIT: I guess another criticism might be "when would you possibly need this?". It smells suspiciously like a symptom of bad design -- these two totally independent, low-level calculations are now dependent on each other. A possible use case might be a tweak to an artificial physics, as in a simulation or game.