If you're comparing to Python (which is stable but hella slow) or Ruby (which tends to be both unstable and slow), GHC stacks up pretty well. By "production systems", I was mostly referring to the sort of things that are normally written in Java or C++ - Google-scale server farms, or financial transaction systems.
You really want predictable execution behavior in those systems. You want to be able to look at a graph of runtime parameters - memory usage, or server load, or QPS, or 95th percentile latency - and see that it's stable, and not have surprises like a massive spike that then shows up as user-visible outages. Because getting paged really, really sucks, and losing money because your customers can't access the system sucks even more. "Kill and restart the process every now and then" is not robustness.
If your intention is to play around with some prototypes and see what you can build, Haskell on the backend and Python on the frontend is a fine choice. Your biggest issue (other than the learning curve of Haskell) will be finding libraries that you can leverage, and Haskell has one of the nicest C FFIs out there. Other than that, here're assorted other little annoyances I've run into.
1. Positional data structures (tuples, "naked" [non-record, no accessor] ADTs, functions with long parameter lists) really suck. You will eventually want to stick another bit of data in there, and then you have to update all the usage sites with new pattern bindings. As immature as the record system is, take the time to use it from the beginning; it'll save you pain later on.
This is a very common pattern in C++ as well; many experienced API designers I know will give all their methods the signature of "bool someMethod(const MethodInput& input, MethodOutput* output)", where MethodInput and MethodOutput are structs. At Google, we often use protobufs as the structs; that gives you a bunch of other benefits, as you can instantly print them out for debugging, and you can log them efficiently, and it becomes trivial to turn the API into an RPC service if it outgrows a single process.
2. Don't be afraid to use type inference; oftentimes, the compiler can infer a more general type than you can write down. If you do write down the types, be liberal with your use of type synonyms; you will probably end up changing those types later, and it sucks to have to visit a few dozen functions with a search & replace. "newtype" is very often worth it, if just for the added typechecking.
3. Point-free style is cool, but I've never been able to look at point-free code I wrote a month ago and understand what it was doing. Don't do this outside of very specific cases where the point-free version has some semantic meaning (for example, you're treating a partially-applied function as an object that does something. If you wouldn't define a closure for it in Python or JavaScript, don't make it point-free in Haskell).
4. Haskell module patterns are much more similar to C than C++ or Java. You typically want a clear divide between "data" modules (that define fundamental dataypes) and "code" modules (which define functions and algorithms acting on those datatypes). Trying to use the OOP-style, "one class per file" approach is very frustrating in Haskell.
5. The "mathematician's naming convention" (all single-character variable names, with "s" as a plural to indicate a list) is just as bad an idea in Haskell as it is in C. Go give your variables some meaningful names and ignore all the tutorials that have "xs" and "ps" and "qs".
6. Very often, you'll want to define a monad for your code from the get-go (you can even just typedef the IdentityMonad to start out with) and write your code in a monadic style from the beginning. You will want to add error-handling and probably state and logging at some point in the future. If you start out with a typedef'd monad type, you can just change the typedef to some monad transformer stack as you need to.
However, you're still suggesting that Haskell is only good for prototyping, not for "real apps", which understandably for you are Google-scale apps. There is a very long distance between what Google does and what I need to do, today, and premature scaling is the death of many projects. But let's be ambitious and pretend that one day I will want to operate at that scale. Assuming C++ is out of the running, is there really no alternative to Java?
I've been working on apps in PHP (Flickr, now Wikipedia) and while I admire the language, in the same way I admire how weeds can colonize an area efficiently, I am not going to choose that for a personal project. I agree with your assessments of Python and Ruby, and I'm not sufficiently enamored of any framework in those languages to make it worth the trouble. I can sling Perl as well as anybody (even in a style approaching functional) and it can be very efficient, but it's hard to find collaborators these days.
This seems to leave Erlang, Haskell, Scala, or maybe Node.js + JavaScript.
Scala seems like what an enterprise would choose for its foray into functional programming, and indeed that seems to be the hopes of its creators. While it's a "safe" choice, for Java shops maybe, I fear the massive shear forces between Java's way of thinking and Scala's attempt to paper over all that with functional thinking. Not to mention the complexity of that language. Still, it seems to work for Twitter, FourSquare, et al.
There's nothing wrong with Erlang, and it has its adherents in industry, but the syntax annoys me.
I'm already an expert in JS, and a codebase of a single language has its benefits. The intense competition in the browser to make JS an application language seems to have already vaulted JS performance over other scripting languages and new libraries are being written every day. Still, it just feels odd to write directly to a database or file in JavaScript. And I've gotten myself tangled up in JS' asynchrony a bit too much recently. Maybe that's an irrational prejudice.
I guess this is why I've been looking on Haskell; in part because it offers some hope of really writing a lot less code and having that code be so well-typed, automatically, that it practically writes its own tests (see QuickCheck) or doesn't even need them. It seems like there ought to be some way to get the best of that without needing to shoot yourself in the foot because you suddenly want to log some action and you didn't define some code twenty layers above it as monadic.
Assuming C++ is out of the running, the alternative to Java is basically Scala. And that's it. Maybe Go will be an alternative in the eventual future, but right now it lacks the library & tool support.
There's also nothing wrong with writing your v1 in one language and then rewriting in a different one if you need to scale. You will probably need to do this anyway, even if you start in C++ or Java, because the architecture needed to scale by an order of magnitude is usually very different from the architecture needed to iterate quickly.
You really want predictable execution behavior in those systems. You want to be able to look at a graph of runtime parameters - memory usage, or server load, or QPS, or 95th percentile latency - and see that it's stable, and not have surprises like a massive spike that then shows up as user-visible outages. Because getting paged really, really sucks, and losing money because your customers can't access the system sucks even more. "Kill and restart the process every now and then" is not robustness.
If your intention is to play around with some prototypes and see what you can build, Haskell on the backend and Python on the frontend is a fine choice. Your biggest issue (other than the learning curve of Haskell) will be finding libraries that you can leverage, and Haskell has one of the nicest C FFIs out there. Other than that, here're assorted other little annoyances I've run into.
1. Positional data structures (tuples, "naked" [non-record, no accessor] ADTs, functions with long parameter lists) really suck. You will eventually want to stick another bit of data in there, and then you have to update all the usage sites with new pattern bindings. As immature as the record system is, take the time to use it from the beginning; it'll save you pain later on.
This is a very common pattern in C++ as well; many experienced API designers I know will give all their methods the signature of "bool someMethod(const MethodInput& input, MethodOutput* output)", where MethodInput and MethodOutput are structs. At Google, we often use protobufs as the structs; that gives you a bunch of other benefits, as you can instantly print them out for debugging, and you can log them efficiently, and it becomes trivial to turn the API into an RPC service if it outgrows a single process.
2. Don't be afraid to use type inference; oftentimes, the compiler can infer a more general type than you can write down. If you do write down the types, be liberal with your use of type synonyms; you will probably end up changing those types later, and it sucks to have to visit a few dozen functions with a search & replace. "newtype" is very often worth it, if just for the added typechecking.
3. Point-free style is cool, but I've never been able to look at point-free code I wrote a month ago and understand what it was doing. Don't do this outside of very specific cases where the point-free version has some semantic meaning (for example, you're treating a partially-applied function as an object that does something. If you wouldn't define a closure for it in Python or JavaScript, don't make it point-free in Haskell).
4. Haskell module patterns are much more similar to C than C++ or Java. You typically want a clear divide between "data" modules (that define fundamental dataypes) and "code" modules (which define functions and algorithms acting on those datatypes). Trying to use the OOP-style, "one class per file" approach is very frustrating in Haskell.
5. The "mathematician's naming convention" (all single-character variable names, with "s" as a plural to indicate a list) is just as bad an idea in Haskell as it is in C. Go give your variables some meaningful names and ignore all the tutorials that have "xs" and "ps" and "qs".
6. Very often, you'll want to define a monad for your code from the get-go (you can even just typedef the IdentityMonad to start out with) and write your code in a monadic style from the beginning. You will want to add error-handling and probably state and logging at some point in the future. If you start out with a typedef'd monad type, you can just change the typedef to some monad transformer stack as you need to.