Erik Meijer: The Curse of the Excluded Middle
CowboyRobot (671517) writes "Erik Meijer, known for his contributions to Haskell, C#, Visual Basic, Hack, and LINQ, has an article at the ACM in which he argues that 'Mostly functional' programming does not work. 'The idea of "mostly functional programming" is unfeasible. It is impossible to make imperative programming languages safer by only partially removing implicit side effects. Leaving one kind of effect is often enough to simulate the very effect you just tried to remove. On the other hand, allowing effects to be "forgotten" in a pure language also causes mayhem in its own way. Unfortunately, there is no golden middle, and we are faced with a classic dichotomy: the curse of the excluded middle, which presents the choice of either (a) trying to tame effects using purity annotations, yet fully embracing the fact that your code is still fundamentally effectful; or (b) fully embracing purity by making all effects explicit in the type system and being pragmatic by introducing nonfunctions such as unsafePerformIO. The examples shown here are meant to convince language designers and developers to jump through the mirror and start looking more seriously at fundamentalist functional programming.'"
"The examples shown here are meant to convince language designers and developers to jump through the mirror and start looking more seriously at fundamentalist functional programming."
Or, perhaps, to acknowledge that it's very hard to do anything useful without side effects.
You can write beautiful, elegant, purely functional code, as long as it doesn't have to touch a storage system, a network, or a user. But, hey, other than that, it's great!
to interact with an imperfect world one needs monads. to have monads is to compromise functional programming. ipso-facto-quod-splut: i always did rarther fancy Fortran. (hsst: don't tell anyone, but Forth is the -only- way to go, (and by 'go' i don't mean "Go" (or "Dart")))
After programming for 16 years, I finally realize I have no idea what I'm doing. I'm so glad these people are out there to point this out.
what does Bennett Haselton think about this topic?
I remember he used to lament the fact that we had to use computers to run programs, because they were always so impure. Hacked up with model-breaking input and output considerations. He loved APL. Had us write our programs as math equations and prove that they had no side effects. On paper. Step by step, like how elementary teachers used to have you write out long division. He was a computer scientist before they HAD computers, he'd point out.
To be fair, APL was a wonderful language, and perfect so long as you didn't want to actually /do/ anything.
Well, that's unfair. As long as you meant to do a certain type of thing, these languages work out fairly well. The issue is the old percent split issue you normally see with frameworks and libraries - by making it easy to do some percent, X, easily, you create a high barrier to performing the remaining percent, Y. The problem with adhering to pure functional languages is that Y is not only high, it's often the most common tasks. Iterating, direct input and output, multi-variable based behavior, a slew of what we'd call flow conditions - these are very hard to do in a pure functional language. The benefit you get is far outweighed by the fact that you could use C, or the non-functional aspects of OCaml, or some other so-called 'multi-paradigm' language to fix the problem in a fraction of the time, even with side-effect management.
Then, have you ever tried to maintain a complex functional program? There's no doubt you can implement those Y-items above. The problem is that it makes your code very specific and interrelated as you're forced to present a model that captures all the intended behaviors. It's a lot of work. Work that will then need to be repeated each time you need to make additional changes. Adding a mechanism to - for example - play a sound at the end of a processing job based on the status - that's a line of code in most languages. Not so in a functional language.
The problem here isn't the oft-cited 'Devs just have to think of things differently, and they'll see it's better.'. It's more basic. It's simple throughput. Functional languages might be a theoretical improvement, but they're a practical hindrance. That, in a nutshell, is why they're not in common use in a corporate environment, where "value" loses it's theoretical polish and is compared to hard metrics like time and cost for a given quality.
I use function techniques even when I'm using Java or C. Writing functions that have no side effects is useful and achievable, even when the language doesn't strictly enforce it.
Furthermore it can be mixed with imperative, or even object, programming. It's a useful technique for minimizing bugs.
"First they came for the slanderers and i said nothing."
I'm not an expect in functional programming, but I am an expert in other (object, etc) styles. While I appreciate the functional toolbox in languages such as Scala (which I use every day), I don't really see a way to do my day to day job in a purely functional way. Others have mentioned the I/O dilemma, but I think it goes deeper than that. Functional != Efficient for many of the tasks I perform, which are rather iterative. For many of my tasks, the overhead of the functional structures required are either much more memory intensive, or impose a run-time overhead that isn't acceptable. In the end, when what I have to do is move 300 fields from one data structure to another with edits, COBOL would be sufficient...
}#q NO CARRIER
The synopsis completely misses the qualification, made in the first sentence, that TFA is discussing "concurrency, parallelism (manycore), and, of course, Big Data". Purely functional programming eliminates some significant issues in this type of programming (while introducing its own set of limitations). Meijer's point is that mostly functional programming is not really better than imperative here
For other types of programming, mostly functional style (using multi-paradigm languages) can be very nice. At least that's my position.
I'll break it down into Retardese for you.
Side effects are things that change the state of the program. We don't want to store the state of the program because it gives it a memory. If it is not memoryless, then it is difficult to reason about. For instance, the equation f(x)=2x+1 is memoryless, because it does not matter what was observed the last time you observed f(x). As such, we can reason very easily with this function, and as long as we always supply the same input, we always get the same output.
Now think what would happen if we have g(x)=h(x)*x+1, where h(x) returns the previous value that it was supplied with (assume that it returns 0 on the first call if you like). That complicates things greatly, because we now have to consider everything that has been called before. You evaluate g(10) then g(5) to get 51, then reset the environment and evaluate g(5) then g(5) to get 26. That means it's no longer a function and cannot be reasoned like it is one. This means that you cannot formally prove the code correct, but you have to use a debugger to hunt down things like some sort of code monkey. It's intolerable!
If Republicans are elected, expect fundamentalist programming to become mandatory.
Am I the only person whose first thought on reading the headline was that Erik Meijer (of all people) should know that the law of the excluded middle is not a theorem in the type theories that he advocates?
sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
"Real world business software developers" are those whose code ends up on The Daily WTF.
But seriously, welcome to the future. In the 1960s, "real world business software developers" thought that all this "object" stuff was a bunch of academic gobbeldygook at worst, or niche tool for people doing scientific simulations at best, rather than anything that would be useful with their hard-nosed COBOL. And in a sense, they were right. How would it help you speed up the overnight bank transaction updates? It probably wouldn't.
This "academic rambling" probably won't help you write your business software today, but it just might help you avoid becoming obsolete tomorrow. Thankfully, you probably won't need to learn it until tomorrow.
sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
newState = deriveNewStateFromDeviceMessage (oldState, message)
(bitsToSendToDevice, newState) = getHardwareCommandForSomeAction(oldState, action)
So, basically, if someone plugs two gadgets into the same control machine, Bad Things happen?
The idea of functional programming is to pass all information in function parameters and return values, rather than through globals.
Forget magic. Any technology distinguishable from divine power is insufficiently advanced.
So Erik Meijer is arguing that mostly functional programming which is widely used in industry "does not work" and suggest instead purely functional programs which have been around for decades and gone nowhere?
Through the looking glass indeed.
From TFA:
mostly secure does not work
Spoken like a true academic. Mostly secure does work in practice. My house is mostly secure, my car is mostly secure, my bank is mostly secure. None of them are perfectly secure, as all of them would fail to a sufficiently strong attack, but generally they do fine.
So does mostly functional programming. It works great in practice even though it is not 100% safe but neither is functional programming once you allow monads which are needed to make FP Turing complete.
It's frustrating. Functional programming is painful when you actually have to do something, not just compute some result. But the real problem is older. We never got concurrency right in imperative languages.
Classic pthread-type concurrency suffers from the problem that the language has no idea what's locked by a lock. This problem is in C, wasn't fixed in C++, and isn't even fixed properly in Go. It was addressed more seriously in Modula and Ada, where the language knew which variables where shared and which were not. The Ada rendezvous approach was too limiting for anything otther than hard real-time, but it was on the right track.
Java addressed this with synchronized objects. This was a step in the right direction. The basic concept of a synchronized object is that, when executing a method of the object, nothing else can affect the state of the object. Java's synchronized objects don't quite get that right - you can call out of an object, then back into it, from within the same thread. This can break the object's invariant, in that the callback function is entered while the object is not in its stable, nobody-inside state. This is a classic cause of trouble in GUI systems, which involve lots of objects calling each other through dynamically changing collections. (If some unusual order of clicks crashes a program, there's a good chance the bug is of this type.)
The inside/outside issue for state protected by locks is a big one. This also comes up when a thread blocks. Many programs have sections where a thread unlocks a lock, blocks, then relocks the lock. This constitutes control leaving the block, but the compiler doesn't understand this. There's no syntax that says "I am now leaving this object to wait", with the language checks to insure that no internal object state gets passed to the code outside the object. The Spec# group at Microsoft (Spec# is a proof of correctness project using a form of C#) attacked this problem, and came up with a solution of sorts, but it never went mainstream. It's hard to fix this with a language bolt-on.
Objects ought to be either immutable, synchronized, or part of something that's synchronized. Then you're safe from low level race conditions. (You can still deadlock. However, deadlock bugs tend to be detectable and repeatable, unlike race condition bugs. So they get caught and fixed.) if this is built into the language, the compiler can check and optimize. Compilers are good at catching things like a local variable being passed to something that might save a reference to it and mess with it concurrently. Humans suck at that. Machines are good at global analysis of big data.
I had great hopes that the Go crowd would have a solution. They claim to, but there's a lot of hand-waving. They claim "share by communicating, not by sharing memory", but the examples in "Effective Go" all share memory. It's also really easy to share memory between goroutines in Go inadvertantly, because slices and dicts are reference objects. Pass them through a pipe and you've shared data and can have race conditions. The problem is bad enough that Google AppEngine limits Go programs to one thread.
Mixed functional/imperative programming has all these problems, plus the illusion that the problem has been solved. It hasn't.
The other way to make code safer, of course, is to eliminate the programmers.
You would think so, but as a programmer I can assure you that over time code changes itself.
No way *I* wrote that...
"There is more worth loving than we have strength to love." - Brian Jay Stanley
But you have to agree that 'functional programming is the next big thing' has been said for so long now - it's the flying car of computer science. When people say 'any day now' for so long, some scepsis *is* in order.
Religion is what happens when nature strikes and groupthink goes wrong.
I've heard this "expressiveness" claim multiple times, but have yet to see a realistic and practical code example. There have been examples where the code was more compact, but the readability of such code was either questionable or highly subjective.
Table-ized A.I.
Pure functional programming means state is isolated not that it doesn't exist.