Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Functional Programming Doesn't Work (and what to do about it) (dadgum.com)
71 points by ColinWright on July 23, 2011 | hide | past | favorite | 54 comments


Functional programming works just fine.

If you want to take two isolated functions and make them behave in some complicated manner depending on how often one or both are called, simply refactor them into the state monad. I.e., replace f: a -> a with f: a -> State a, and do the same for g.

This is likely to break stuff. In C, good luck figuring out what might be broken as a result. In a pure functional language, you know the damage is limited to the state monad. Anything outside of state is safe.


It took me a while to realize, but monads are a great solution to the problem of state (to mention just one thing).

Now that I've gotten used to them though, it sucks not having them in (many) other languages... Haskell has spoiled me :(.


Most other (good?!) languages should allow you to define monadic data structures fairly easily. Many languages are able to make use of monads, the difference being that Haskell leaves you (almost) no other option.


Refactoring a stateless program into one that uses state is a big pain. Making programs that use different amounts of state, or kinds of state, at different parts within the program, is also a big pain. Functional programming languages including ones where monads are regularly used are just bad at this. You get towers of state transformers, or a lot of boilerplate for converting between one kind of state to another, and boilerplate for entering and leaving different kinds of stateful situations. Or you can just have one big state monad that goes everywhere, in which case there's three kinds of things, which are functions that live purely in IO, those which are pure, and those which live in your state monad.

But then you realize that certain purely IO actions need to do some logging and then they can't live in the IO monad and you need to refactor logging out of everything else and put your logging monad inside your state monad (and put a bunch of new liftIO's all over the place), but wait, didn't you just want to add some logging statements to debug this thing, and now you're tearing apart the structure of the world.

So maybe we could come up with some abstraction for logically separate components of a program's state, and then create a type of action that uses descriptors telling what subset of a program's state components are used, or we could not do that and recognize that functional programming does not work just fine, and that pure functional languages are bad at working with state.


I agree with you that Haskell's approach is imperfect. There are too many liftIO's, and some method of taking the union of state monads (rather than nesting them) would be vastly better.

But I'm going to disagree with you regarding the claim that pure functional languages are bad at state. All of the problems you mentioned also exist in a non-functional language. In a non-functional language, you have functions which live in IO, in StateT GameState IO, and pure functions. The only difference is that you must carefully read the code to figure out which is which.

The approach of having one gigantic state monad which goes everywhere is precisely what you do in an impure language - just with some syntactic sugar to hide the liftIO.


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

Why would anyone want to do this? This sounds like a really, really bad idea.

I'm sure there are legitimate problems that are easier to solve with an imperative language than a functional one (such as quicksort, perhaps), but this article doesn't provide any.


If you are designing a program, even in an impure language, it's a good idea to design it so that it never has to do this. It's confusing and bad style.

But if you've already designed the program, and you want to make a semantically minor change that requires linking formerly unrelated parts of the program, an impure language lets you do it without modifying half the program to keep up with new signatures.

I think the fact that the author is writing games-- where the user literally interacts with a big mutable world, and "when A happens, B should happen" is a natural request-- magnifies the problem, but it could happen anywhere.

Actually, I can think of an example: in New Super Mario Bros. Wii, there is a nice toy feature where when the music reaches a certain point, the enemies dance to it: http://www.youtube.com/watch?v=s03Kco-XQCY . I don't know anything about the actual development of the game, but as this doesn't significantly affect gameplay, I could imagine that this feature was added late in development, making what was formerly a one-way pipe from gameplay to sound into two-way communication. In a well designed program in a pure language, wouldn't this be hard to implement?


But if you've already designed the program, and you want to make a semantically minor change that requires linking formerly unrelated parts of the program, an impure language lets you do it without modifying half the program to keep up with new signatures.

I'd rephrase this slightly.

If you want to make a semantically minor change that involves linking formerly unrelated parts of a program, an impure language permits you to do so without thinking through the effects this might have on your whole program.

In contrast, a pure program forces you to walk through your code and replace "a -> a" with "a -> GameState a" in every single function that might have broken. This includes the functions you might have forgotten about.

Now lets look at how this would explicitly work in your example of dancing enemies. In a pure functional language, your game logic is probably something along the lines of GameState a = State GameWorld a, with GameWorld a type describing the state of your world.

To add the dancing koopa troopa logic, you'd merely need to modify the GameState object and include (MusicTrack, MusicTime) as a field. There would be very little messing around with type signatures - the function advanceEnemyState: () -> GameState EnemyState is already expected to vary based on the world.


Minor[1] nit: quicksort is not a problem, it's a solution to a problem. The problem in question in efficient general-purpose sorting.

1. It may not be such a minor nit. Too often comparisons are made based on implement algorithm X where the algo is something that is specific to, say, imperative programming. This leads to endless and mostly meaningless arguments.


Agreed, but just because: Quicksort in Haskell:

  quicksort [] = []
  quicksort (x:xs) = quicksort front ++ [x] ++ quicksort behind
     where front = filter (<= x) xs
           behind = filter (> x) xs


I hate this example. This is not what quicksort is about. Quicksort is about sorting array in place. The preceding code is not. Also I can't see how it's relevant to the thread.


You know, I actually thought about mentioning that it wasn't quite exactly the same, but decided against because I really do think that it's still quicksort. Is it identical? No, you're right, it's not. But it still is an expression of the same algorithm.

Modifying the array in place is a inherently imperative technique, and while you /could/ do something like that in Haskell using, say, Data.Array.ST, you would loose the expressiveness of programming functionally and be programming imperatively just for the heck of it.

Trying to solve a problem in a functional language exactly the same as in an imperative one is foolish. They're different paradigms with different strengths and weaknesses, and if you try to map concepts 100% from one to another, it will often end in tears.


That's what I actually meant -- quicksort shows where the imperative approach shines, and using it as an example of functional programming is a particularly bad idea, because naive implementation is not relevant and serious one (in place sorting of random-access sequences) is quite complicated and not elegant at all. It's just the example of the fact that not all algorithms can be efficiently implemented in purely functional manner, just like not all algorithms can be elegantly implemented using only imperative techniques. There's no silver bullet.


Quicksort is the demonstration of the divide-and-conquer strategy, which is just as useful in languages that enforce immutability as it is in languages that allow mutation.

The fact that you can update in-place is a nice bonus that gives the algorithm great performance and space characteristics in languages with mutable datastructures, but that implementation detail is not an inherent part of the beauty of the algorithm.

It's relevant because weavejester suggested quicksort is easier to implement in an imperative language.


No, the demonstration of divide-and-conquer strategy is the merge sort. The whole point of quicksort is that it is in place, which makes it fast in spite of the pessimistic quadratic time -- that's why it is called quicksort, actually. This naive implementation is neither fast, nor in place, nor quicksort.


No, the demonstration of divide-and-conquer strategy is the merge sort.

So is Quicksort. There needn't be just one algorithm that demonstrates divide and conquer.

The whole point of quicksort is that it is in place, which makes it fast in spite of the pessimistic quadratic time

That's not the whole point of quicksort. It is done in-place, but that's just a great side effect of the algorithm. But assuming a cacheless memory model (for example the RAM model) the complexity of the out-of-place version is unchanged. The great thing about QS, IMO, is that it's easy to implement and typically O(n log n). When it is quadratic it is slow -- being in-place only barely makes the quadratic version more tolerable.

With that said, I know what you're saying :-)

If you read "Purely Functional Data Structures" there are a lot of tree/graph manipulations that are cake to do in C, but are a bear in ML/Haskell. A great programmer has all tools at her disposal, not just one (even if it is finely tuned).


Am I the only one that sees a big difference between functional programming and what the author is describing? For me, a functional programming language is one that allows for closures, inline functions, and functions as first class values (allowing higher-order functions such as map and fold). The author is describing what I would call a purely functional language, or even better, referentially transparent functions.

The author's whole point about SSA has nothing to do with purely functional programming. It's funny that assignment to variables has nothing to do with referential transparency - you can easily create as many true variables in a function (locally), assign all you want, and still have a purely functional / referentially transparent function.


> For me, a functional programming language is one that allows for closures, inline functions, and functions as first class values (allowing higher-order functions such as map and fold).

I disagree. A language may have all those things and still be imperative. Smalltalk is regarded as one of the first and most importat OO languages (i.e. imperative) and has all those things you mention.

I think functional programming _is_ about pure functions, side-effect-free code and, in general, declarativity instead of an imperative style of programming. However, even the most "pure" functional programming languages, like Haskell, have some way of writing imperative, side-effect-full code. After all, you _need_ those side effects to make the program _do_ something hehe.


> OO languages (i.e. imperative)

Why can't an OO language be functional? OCaml is OO, and it's one of the most functional languages I know (the other being Haskell). Scala is a functional language and an OO language (and an imperative language, as a matter of fact). Clojure could also be considered OO, with the new protocols. Javascript is a very OO language, and quite functional as well (especially considering the libraries such as Underscore). Maybe we should be talking about functional and imperative programming styles instead, imperative being a loop, and functional being a recursive function (map, filter, fold/reduce).

> declarativity instead of an imperative style of programming

I guess there's a thin line separating these concepts, and also a great deal of overlap and confusion. Here, we're declaring what we want:

  even = [i from nats[1:100] where i % 2 == 0]
Here, we're using "the imperative style":

  even = []
  for i = 1 to 100
    if i % 2 == 0
      even.append(i)
This is functional style:

  even = filter(nats[1:100], i -> i % 2 == 0)
However, this is also imperative - we're telling the computer what to do. The main difference is just that in the "imperative style", we're dealing with elements, while functional style is focused around sequences.

On the other hand, these concepts and expressions are used with so many different meanings and in so many different contexts, that it's no surprise that different people understand them differently. Maybe it's better to just talk in code...


> OCaml is OO

Interestingly, the class system of Ocaml is one of its least useful parts. This is basically because most uses for inheritance (mixins, interfaces) are covered by first class functions.

> However, this is also imperative - we're telling the computer what to do.

No we don't (if you except the assignment to even). The fact that "filter" is an active verb is slightly misleading here. It deludes us into thinking the computer does something. (Well, it does, but not in the imperative programming sense of the word.) A more correct name would be "filtered", which makes it clearer that it produces a result, which is a filtered version of its second argument. I we wanted to be pedantic, "filter" should be reserved for a procedure that actually modifies its argument in place. But as there's no such procedure in the standard library of the typical functional language, it doesn't really matter.


"Imperative and object oriented are two different things" - Martin Odersky. An object is just a something that implements an interface and an interface is just a collection of functions. It may or may not have mutable state. That's why OO and functional are really just two sides of the same coin. I think that tightly associating a set of functions with data is still a very good way to organize programs for the same reasons that it always was. Its the over use of the imperative bit of many languages that caused all the problems.


> > OO languages (i.e. imperative) > > Why can't an OO language be functional?

Yes, I think you are totally right. As I see it, OO is about data and behaviour encapsulation, and object polymorphism. There is no reason why objects need to carry mutable state. But, they often do, and I think most authors do put OOP in the procedural/imperative side of programming.

Of course come cool hybrid languages do mix concepts from OOP and functional programming in order to have more expressive power. But most of them still map pretty closely to the Von Neumann model: a set of statements executed one after the other that alter the program's state, and hence are imperative too.

A functional program does not map it's computation to this Von Neuman model, which pretty much represents how computers work, but to an abstract model, usually based on mathematical concepts.

Therefore,

  even = filter(nats[1:100], i -> i % 2 == 0)
would be functional or not depending on the underlying execution model. A functional programing language would guarantee that the function passed to `filter` is side-effect-free, so it can be free to make the computation of the result in parallel or lazily or whatnot. An imperative language would be required to execute the passed function for each element of the sequence in a defined order, as that function might very well not be side-effect-free.

But, in the end, I think that all that is a very absolutist approach to what is functional and what is not. Trying to draw that line is usually quite futile, as it really depends on very strict definitions, and, well, everybody agreeing on those definitions. I just wanted to clarify my point a little bit (though I probably failed)

> On the other hand, these concepts and expressions are used with so many different meanings and in so many different contexts, that it's no surprise that different people understand them differently. Maybe it's better to just talk in code...

Totaly agreed. Probably a more "fuzzy" definition of "functional", thus allowing one to say that some construct "more functional" than an other one, is more pragmatic. I also would say that

  even = filter(nats[1:100], i -> i % 2 == 0)
is much "more functional", or declarative, than

  even = []
  for i = 1 to 100
    if i % 2 == 0
      even.append(i)
And I would even say that Python's list comprehensions are even more declarative, even though it is an imperative language too:

  even = [i for i in range(0,100) if i % 2 == 0]
Code FTW!


You're getting mixed up with what a technical form of functional programming is (which can be summarized with "nothing changes", which is more inline with your "pure" quantifier; try this: http://academicearth.org/courses/the-structure-and-interpret... ), and with features of what members of the Functional Family tend to have.

If you ignore closures, C has "inline [anonymous functions]" as well as "first class functions" in the form of function-pointers. The fact that the syntax is less than pleasant doesn't mean you can't do those things in it. C is also Object-Oriented-Capable, just like C++. Ignoring the C debates, you can even go up to ActionScript/Javascript which have those features you described, yet I wouldn't call them functional programming languages even if you can write functional code in them. They don't embody the ideal of "nothing changes" that, as you say, is more "purely functional". What member of the Functional Programming Language family loves the idea of side-effects? They all support it begrudgingly of course.


I don't see how function pointers allow inline or anonymous functions. They point to already-defined functions, which can't be defined inline or anonymously.



That's not really C, nor is it a property of function pointers in that example — it's a nonstandard GCC feature that happens to be returning a function pointer in that particular instance. If you know of a way to do it without requiring a non-C feature like expression blocks, I'm all ears.


The same argument could be made about structured programming: "Why should I restrict my programs to have one entry point, one exit point and a single flow?"

The answer is "Because it helps you to manage complexity in the long run".

To continue the "just add a simple global" analogy: after a while you add another, then you peek from a completely different function inside the global variable, modify it if the day is odd and soon you have made a mess of your program. In this case globals are simply a patch to an outdated design.

"It [all the restrictions] says a lot about what functional programming isn’t (it has no assignment, no side effects, no flow of control) but not much about what it is. The functional programmer sounds rather like a mediæval monk, denying himself the pleasures of life in the hope that it will make him virtuous. To those more interested in material benefits, these “advantages” are totally unconvincing."[1]

But the new kinds of glue functional programming brings about are usually worth the squeeze (for some kind of programs in some kind of situations, maybe).

[1] http://www.cse.chalmers.se/~rjmh/Papers/whyfp.pdf


Could people please stop use "Functional Programming" as a synonym for "Pure Functional Programming"? The author uses them interchangeably even though his arguments do not apply to the impure style.


Even in impure languages, writing simple functions without side effects is the ideal of functional programming — the language just doesn't force it. If you want to write imperative code, just call it imperative. Don't call it functional just because that's the cool buzzword nowadays.


And whilst we're at it can people stop using "water" as a synonym for "pure water". Mud is water too, it's just not as pure!


Terrible, terrible, terrible analogy.

The ecosystem of programming languages are analogous to class&race interplay in RPGs, especially RPGs where youve got strong distinctions between melee (imperative) and casting (functional) classes, with a smattering of hybrids (Python, Ruby, Scala, Clojure).


anon_d has a point, im interested in the same take on using scala and other hybrids.


I think that's his point.

"After spending a long time in the functional programming world, and using Erlang as my go-to language for tricky problems, I've finally concluded that purely functional programming isn't worth it." (emphasis mine)


s/use/using/. I hate not being able to edit my comments.


I concur. I've been working on http://fexl.com for some time, and when all is said and done, for my embedded scripting applications I prefer to use simple line- and token-oriented languages, where everything looks like this:

  word word word
  word word
  word word word word
  ... etc.
The semantics of these embedded languages tend to be domain-specific, or in the cases where I need Turing-equivalence, revolve around the notion of a general "whiteboard" where the program can scribble key-value pairs, and do basic conditions and looping. I find that non-programmers can follow this sort of logic very easily.

Nice thing is, I can write these interpreters in any language, such as C or Perl or whatever, and "sandbox" their semantics in an airtight way for security.


The modularity advantage of shared state's "spooky action at a distance" when properly employed is not a fresh insight. It's even addressed in good textbooks on programming such as van Roy and Haridi's Concepts, Techniques and Models of Computer Programming. Here's an old related LtU post by van Roy: http://lambda-the-ultimate.org/classic/message9361.html. Another dramatic example (which is one of the book's exercises) is revocable capabilities.


not a fresh insight

James Hague would know this as he was the second replier to van Roy in the thread you cite. Freshness of the insight is hardly the point.

I'm glad you posted it, though. It's a good discussion. I particularly agree with this formulation of van Roy's: It is the overall simplicity of the system that counts, not whether it is written in a functional or stateful style. Using true state in the right places makes the system simpler overall.


My comment was not meant to disparage James, so there's no need for you to valiantly leap to his defense. It was just an attempt at putting his observation into a wider context. :)


Title: Functional Programming Doesn't Work

Content: At this point I should make it clear: functional programming is useful and important.


Original discussion: http://news.ycombinator.com/item?id=1020130

Original discussion on follow-up article: http://news.ycombinator.com/item?id=1023232


"No reason to add extra complexity just to dodge having a single global variable"

Yes, yes there is. It's called "reducing side effects".


In all fairness, "reducing side-effects" is a means; "ease of composing a program," "ease of understanding a program" and "ease of extending or changing a program" are ends.

They're all reasons, but it's possible that adding complexity to facilitate a means may compromise the end, aka "winning the battle but losing the war."


Anyone who's been a programmer very long has pulled their hair out over programs which have been extended far beyond the intentions of the original developer. It's not the right reaction to make everything as extensible as possible either, but eventually one learns a healthy respect for the dangers of global variables.

Pacman is a toy application by today's standards. Imagine developing an MMO. Sometimes the biggest part of the problem is precisely figuring out what state lives where authoritatively and exactly how far out-of-date each asynchronous copy can be safely allowed to drift.


Why care about converting to SSA, when your objective is to convert to CPS?


I stopped reading when I got to this gem:

"It can be done quickly and cleanly by adding some global variables."

We differ by orders of magnitude on what constitutes 'cleanly'.


I suggest that you read the whole sequence of articles, particularly the ones where he tries to implement Pac Man in a purely functional way.

Keep in mind that he is an Erlang fan, and he is not trying to "debunk" functional programming. He's just toying with applying it to a domain where it's not used often (action video-games). The whole series is excellent, as is, in fact, the whole blog.


Except that he doesn't try and implement it in a pure, functional way.

Every linked article discusses his adventures with Erlang. Erlang is a great language, one that I've used to great effect and enjoyed very much, but Erlang is really bad at representing rich type relations. The author is absolutely right that records are a painful substitute for dictionaries (a construct he reached for naturally because Erlang is a dynamic language).

He tried to implement it in Erlang's curious and somewhat isolated pseudo-functional rigid-actor paradigm. People have been making games and game-like demos in Haskell and OCaml for quite some time now (OCaml especially seems like very good fit). Erlang is a beautiful tool designed to solve one very specific set of problems. In its domain, it is nearly unrivaled. Outside of its domain, it is lackluster and limiting. It's only recently that modern functional programming techniques like monads and a cheap lambda syntax have started to make their way into Erlang (see Erlando: http://www.rabbitmq.com/blog/2011/05/17/can-you-hear-the-dru...), so it should be no surprise that someone writing in 2008 found a lot of awkward edges.¹

Am I wrong? I quickly scanned every linked article (and their follow-ups) and didn't see a single mention of anything but Erlang, including an all-too-common digression into using tuples as a dynamic substitute for algebraic data types.

¹ I can't help but feel like people assume most Haskell and OCaml programmers are chronic masturbators; making constructs like Monads and Functors because they just love category theory instead of the reality; building a functional pattern language on the same order as what Gamma, Helm, Johnson and Vlissides captured for OO programming.


His solution is the "problem" of having to update the whole game state in a function is "is not to return new versions of anything, but to simply return statements about what happened" and "Actually handling what happened is a separate step, one that can be done later on in the frame."

Did he just make an imperative DSL out of erlang?


He's talking about the smallest and simplest of global variables in that quote, hence:

"it's completely doable to write [Pac-Man] in a purely functional style. The dependencies can be worked out; the data flow isn't really that bad. It still may be a messy endeavor, with lots of little bits of data to keep track of, and selectively moving parts out of the purely functional world will result in more manageable code. Now the obvious target is either the state of Pac-Man himself or the ghosts, but those are part of the core data flow of the program. Make those globally accessible and modifiable and all of a sudden a large part of the code has shifted from imperative to functional...and that wasn't the goal."

meaning that trying to make Pac-Man or ghosts into pieces of mutable state will result in them being manipulated entirely by being passed between functions, an even more functionally tangled program (in terms of less being able do some modifications to it as easily as could be done in a similar imperative program.)

Instead he's suggesting adding tiny self-managed globals for little values that truly are global (used almost everywhere) but aren't constant. Basically segregating tiny pieces of mutable state into their own little cubbyholes in order to simplify the logic.

Or at least that's how I'm reading it.


"Instead he's suggesting adding tiny self-managed globals for little values that truly are global (used almost everywhere) but aren't constant. Basically segregating tiny pieces of mutable state into their own little cubbyholes in order to simplify the logic."

That kinda sounds suspiciously like a monad.


It also sounds suspiciously OO.


kinda does...


- When you change a function b so that it depends on the number of times a has been called, then this is a pretty deep change; but yes, it should be simple to do in situations like logging etc.

- Single-assignment really has NOTHING to do with purely functional programming; see my article "Purely Functional Structured Programming" or my (purely functional) programming language Babel-17 (www.babel-17.com)

- Random is an interesting operation; it is not purely-functional, but can be considered side-effect free; so it is not really imperative, either (although implementations via seed are)


It is remarkable stupidity to post such nonsense on the site which is a reality-check example of the exactly opposite. ^_^




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: