Slashdot Mirror


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.'"

43 of 237 comments (clear)

  1. Jump through the mirror? by jeffb+(2.718) · · Score: 5, Insightful

    "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!

    1. Re:Jump through the mirror? by jythie · · Score: 4, Insightful

      I take this as a good example of why we should not strive for universal languages to use everywhere. Functional languages are really useful in their domains, while procedural and OOP languages are really useful in their domains. Language designers, or maybe just programmers who do not want to learn more then one language and thus find the One True Language, keep trying to extend languages to handle everything.

    2. Re:Jump through the mirror? by catmistake · · Score: 5, Funny

      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!

      Speaking as an sysadmin, sounds like Heaven.

      The other way to make code safer, of course, is to eliminate the programmers.

    3. Re:Jump through the mirror? by PPH · · Score: 3, Funny

      Speaking as a sysadmin, you should have said, "eliminate the users."

      --
      Have gnu, will travel.
    4. Re:Jump through the mirror? by fractoid · · Score: 2

      Or, perhaps, to acknowledge that it's very hard to do anything useful without side effects.

      Exactly. Outside of an ivory tower or some very niche applications, everything we do with computers inherently has side-effects. Trying to pretend otherwise just leads to elaborate castles in the sky.

      --
      Rampant carbon sequestration destroyed the Dinosaurs' tropical paradise. I'm here to help repair the damage.
    5. Re:Jump through the mirror? by Anonymous Coward · · Score: 5, Informative

      Nobody is trying to pretend that there are no side effects you idiots. The point is that there are a lot of benefits that come when you clearly separate the parts of the program that have side effects from those that are pure and referentially transparent.

      It's trivial to ask a user for input, send packets across the network, query a database in Haskell and various other purely functional programming languages.

    6. Re:Jump through the mirror? by jc42 · · Score: 4, Funny

      ... C programmers are brain damaged too.

      From the viewpoint of the general public, we can simplify it to "Programmers are brain damaged." This conclusion is often especially obvious to management, who find programmers both incomprehensible and arrogant. After all, would a truly sane person clearly tell their bosses that something can't be done the way the bosses said it is to be done? But programmers do this all the time, so they must be mentally deranged.

      --
      Those who do study history are doomed to stand helplessly by while everyone else repeats it.
    7. Re:Jump through the mirror? by Pseudonym · · Score: 5, Informative

      My impression of "pure" functional programming is that it's roughly like having only static classes and no static members in OOP.

      If you got that impression from TFA, then I can actually understand how you got it. Meijer's article was written for people already using not-fully-pure functional languages, where "class" means something slightly different than it does in a Simula-style OO language.

      The term "class" comes from von Neumann–Bernays–Gödel set theory. Naive set theory had issues like Russell's Paradox, which relies on notions like the "set of all sets". To remove this paradox, NBG set theory distinguishes between a "set" and a "class" (i.e. a collection of sets defined by a property they have in common). Some classes are sets, and some are not. A set is a collection of values, but a "class" is a collection of sets.

      In programming language theory, a "type" can be thought of as a set of values (e.g. "boolean" might be the set {true, false}). A "class" is a collection of types.

      When you write class Foo in (say) Java, you're actually doing three separate things.

      • You are declaring a new type, called Foo.
      • You are declaring a new collection of types (also confusingly called Foo), which will turn out to be the type Foo and all its subtypes.
      • You are declaring that the type Foo is a member of the collection Foo.

      In the Haskell class system, these three things are separated. This is why Haskell classes look more restrictive than classes that you might find in Java: a Haskell class only contains the parts that make it a class, not the parts that make it a type.

      Did that help?

      --
      sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
    8. Re:Jump through the mirror? by rabtech · · Score: 4, Interesting

      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!

      This is a huge misconception about functional programming, one that I used to have myself.

      With a functional programming language, you can have side effects, you are just forced to be explicit about those side effects with specific language features in specific places.

      Basically functional programming requires you to "opt-in" to side effects only where necessary.

      Traditional imperative programming requires you to "opt-out" by taking huge steps to enforce immutability, generating mountains of code to accomplish any task because the compiler doesn't help you.

      --
      Natural != (nontoxic || beneficial)
    9. Re:Jump through the mirror? by Belial6 · · Score: 2

      You can simulate the exact same effect by just quiting your job and never working in the field again.

    10. Re:Jump through the mirror? by Belial6 · · Score: 2

      As illustrated here: https://www.youtube.com/watch?...

    11. Re:Jump through the mirror? by ozmanjusri · · Score: 4, Funny

      Suddenly Win8, the Office ribbon, RT, and Sharepoint begin to make sense. Thanks for the epiphany.

      --
      "I've got more toys than Teruhisa Kitahara."
    12. Re:Jump through the mirror? by gnupun · · Score: 4, Insightful

      Linus was right. OO programmers are brain damaged.

      If you use fopen(), fread(), fclose() etc., you're already doing object oriented programming in C.

    13. Re:Jump through the mirror? by gstoddart · · Score: 3, Informative

      Yes. With increasing CPU speed, it makes sense to sacrifice "performance" for ease of programming.

      I worked with a guy who used to say that.

      He wrote shitty, un-maintainable code which he thought was elegant, and which in practice was garbage, full of ridiculous assumptions, and giant inefficiencies all in the name of him being able to invoke something in as few lines of code as possible, or without consideration for the cost of his framework. Half of his code went through massive setup tasks every time it was invoked or naively did something assuming it wasn't expensive. Over and over and over again.

      He said you should write first, and the optimize later. By the time we realized his code was so slow as to be unusable, he had painted himself into a corner, and there was no way to optimize his code except to get rid of it.

      Sometimes the things passed off as "ease" of programming is a thinly veiled decision to use known terrible methods in the expectation they're prettier.

      In my experience, some of these claims don't produce good code. They produce things the coder believes to be pretty, but which in practice is quite a mess.

      I've yet to be convinced you should start writing your code in a way you know is inefficient because your belief in your elegant solution, which is anything but. I've seen an O(n^2) algorithm called O(n^2) times, all because it was "cleaner" code, and the code assumed everything was a zero cost operation (or had hidden all of the aspects of that, so you don't find it until later).

      Performance is a real thing. I lament that people have now decided it's irrelevant to consider it.

      --
      Lost at C:>. Found at C.
    14. Re:Jump through the mirror? by jbolden · · Score: 2

      Class definition of Haskell simply contains methods defined abstractly.

      For example the collections class is defined:

      class Collects e ce where
                    empty :: ce
                    insert :: e -> ce -> ce
                    member :: e -> ce -> Bool

      What this reads is there are 3 functions
      empty which tells you if a collection is empty
      insert which takes an element and a collection is arguments and then "inserts" the element into the collection
      member which takes an element and a collection is arguments which tests to see if an element is a member of a collection

      That's it for the class. If you have a collection you are guaranteed those 3 things exist. But you guaranteed nothing about implementation at all. Because of that the methods empty, insert, member are incredibly polymorphic. Which means I can continue to define new functions using these abstractions. So "insert list"

      insertList list collection = foldr insert collection list
      and I get an efficient operation involving a looping structure with no complexity at all.

    15. Re:Jump through the mirror? by rwa2 · · Score: 2

      Well, would you rather be faced with a false dichotomy, or would you like to have other options?

    16. Re:Jump through the mirror? by gnupun · · Score: 2

      OO (C++) promotes data hiding (of fields in a struct/class) and using member functions to manipulate the data in a safe manner. So the above file operations in C++ would be something like:

      f = new File("test.txt", "rb"); // create file object
      // f = fopen("test.txt", "rb"); // C code
       
      f->read(dataArr, 1024); // read 1KB array
      // File::read(f, dataArr, 1024); // compiler generated code
      // fread(dataArr, 1024, f); // C code
       
      f->write(dataArr, 1024); // write array to file
      // File::write(f, dataArr, 1024); // C++ compiler gen code
      // fwrite(dataArr, 1024, f); // C code
       
      f->close();
      // File::close(f) // C++ compiler gen code
      // fclose(f) // C code
       
      delete f;

      Note: I've skipped the "size" parameter in fread(), fwrite() for simplicity.

      The C "FILE*" type is object oriented in that
      a) The programmer has no idea what's inside the struct (same as OO data encapsulation)
      b) You have to use fxxx() functions to access anything about the file (continuation of data encapsulation)
      c) C programmers pass an "fp" variable to the fxxx() functions whereas C++ programmers pass an invisible "this" pointer to similar file member functions.

      As you can see, under the hood, the code generated by the C++ compiler is very similar to the C code you are familiar with when using fxxx file functions because C FILE objects use many of the concepts used commonly in C++.

  2. functional programming catch-22 by smoothnorman · · Score: 3, Interesting

    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")))

    1. Re:functional programming catch-22 by Pseudonym · · Score: 2

      to have monads is to compromise functional programming.

      [citation needed]

      --
      sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
  3. Wow by xdor · · Score: 3, Insightful

    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.

    1. Re:Wow by PPH · · Score: 3, Funny

      P.S. You still login to Slashdot so you're almost definitely an incompetent retard.

      Welcome to the club. Here's your hat.

      --
      Have gnu, will travel.
  4. Well that summary leaves me wondering by Anonymous Coward · · Score: 5, Funny

    what does Bennett Haselton think about this topic?

  5. Sounds like my old comp-sci professor. by quietwalker · · Score: 5, Insightful

    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.

    1. Re:Sounds like my old comp-sci professor. by msobkow · · Score: 2

      And I am far from an Erlang expert. Therein lay the problem. The only expert at the language who recommended using the language was the first one to fuck off and run away when the going got tough and it was too late to change course to a language the whole TEAM was familiar with, like Java or C#.

      --
      I do not fail; I succeed at finding out what does not work.
    2. Re:Sounds like my old comp-sci professor. by Pseudonym · · Score: 3, Insightful

      Erlang expressed arrays as lists internally.

      Actually, it uses a 10-ary tree, which has O(log n) lookup, update, etc with a pretty low constant factor. Or, at least, that's what it does now.

      It doesn't have to be this way. Haskell has O(1) arrays, but they live in a monad (either ST or IO) if they're not read-only. Plus, of course, Haskell has lazy evaluation, so read-only arrays are not necessarily as read-only as you might think.

      Without knowing more details (and since I'm just some random guy on the Internet, you probably don't care enough to give details, so that's cool) it's difficult to say if you really needed imperative arrays for your algorithm, or you only thought you did because that's what the Visual Basic prototype used. Most people don't learn pure functional data structures in their undergrad classes, so they not always the solution which springs to mind when you have a problem to solve.

      --
      sub f{($f)=@_;print"$f(q{$f});";}f(q{sub f{($f)=@_;print"$f(q{$f});";}f});
    3. Re:Sounds like my old comp-sci professor. by msobkow · · Score: 4, Interesting

      No offense taken. I don't claim to be an Erlang expert; I hadn't even heard of the language before this project. None of the team members had worked with it. The only one who'd worked with it was the guy who architected and prototyped the system. As soon as he was done the prototype, he didn't renew the contract and buggered off.

      But we had done "too much" to switch to a language we could all agree on. Oh hell, no. We had to keep on using that crap because somebody had Made A Decision and wouldn't backtrack and Lose Money.

      In the end, they lost 4-5 times as much money when we couldn't make it go. And it serves them right -- sticking with a bad decision just because you've got an investment in it is stupid when everyone is telling you it's a bad decision and a bad investment. You need to listen to the TEAM DOING THE WORK, not an "expert" who buggered off before the real work started.

      --
      I do not fail; I succeed at finding out what does not work.
  6. Not really by phantomfive · · Score: 3, Insightful

    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."
    1. Re:Not really by phantomfive · · Score: 2

      Well, functional programming won't eliminate bugs either, so whatever

      --
      "First they came for the slanderers and i said nothing."
  7. Good aspiration, bad in (some) practice by RedMage · · Score: 2

    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
  8. Bad Summary. by thestuckmud · · Score: 4, Interesting

    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.

  9. Re:I am looking for a word ... by Anonymous Coward · · Score: 2, Informative

    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!

  10. It's Not Just A Good Idea, It's The Law by mbstone · · Score: 2

    If Republicans are elected, expect fundamentalist programming to become mandatory.

    1. Re:It's Not Just A Good Idea, It's The Law by DahGhostfacedFiddlah · · Score: 2

      Yeah, it seems they're always trying to reduce the power of the state.

  11. Excluded middle, eh? by Pseudonym · · Score: 2

    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});
  12. Re:Gobbledigook by Pseudonym · · Score: 4, Informative

    Real world business software developers just don't talk that way.

    "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});
  13. Re:State machine programming by ultranova · · Score: 2

    I write lots of state machines that control external world gadgets, which input new results to my program to use to compute the next state. Consequently I can only wonder about this idea of removing side effects as: "WTF?"

    newState = deriveNewStateFromDeviceMessage (oldState, message)

    (bitsToSendToDevice, newState) = getHardwareCommandForSomeAction(oldState, action)

    That said, I tend to write modules so that they only write to globals within that module.

    So, basically, if someone plugs two gadgets into the same control machine, Bad Things happen?

    Writing an IIR filter without memory is a pretty funny idea.

    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.

  14. Through the looking glass indeed by Alomex · · Score: 2

    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.

  15. Mostly functional works quite alright by Alomex · · Score: 4, Insightful

    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.

  16. Concurrency is still badly understood by Animats · · Score: 4, Interesting

    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.

  17. No safer by SuperKendall · · Score: 4, Funny

    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
  18. Re:Gobbledigook by bytesex · · Score: 2

    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.
  19. Re:Safer is not the reason by Tablizer · · Score: 2

    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.

  20. Re:The only true functional program... by jbolden · · Score: 3, Informative

    Pure functional programming means state is isolated not that it doesn't exist.