Pretty much everything starts out life as a thunk, and the compiler does its best to translate these into strict values which affects when things are evaluated and therefore the performance, whether things can be stored on the stack fully evaluated, as registers or boxed things on the heap that need forcing, etc.
The good news is that purity makes it significantly easier to do inlining and reducings/transformations bordering on whole-program compilation. Code that looks like a load of function calls usually gets compiled down (if you use the -fext-core flag) to a couple case expressions.
You have to do this to get reasonable, baseline, performance in Haskell. In OCaml—or ML, or any strict, mutating language—you get that for free, as a starting point. They mirror the architecture underneath so they're already fast from the get-go.
It's not to do with type checking or anything like that. Type checking with GHC is very fast. If you use -fno-code, it can compile, e.g. a 9Kloc codebase of 65 modules in 1.361s on my machine. However, if you enable code generation, it takes 9.532s and that's with -O0: with -O2 it takes 32.910s. Fay, my unoptimized, non-optimizing Haskell→JS compiler will codegen that codebase in 1.940s. It would do it in less if I optimized the compiler's parser and codegen. But it's fast because I don't do anything. I don't do any flow analysis or strictness analysis or simplification or generation of core or compiling to C--. Fay's generated JS is not fast as a result. Compare that to if I were writing a Lisp compiler to JS, which would be easy because the source and the target are basically the same execution model.
If you're on a project like a Yesod app, then you have to conservatively rebuild a lot more than you would normally because TH doesn't lend itself well to incremental compilation and the compiling itself is slower too. Depending on the size of the project you're facing 5-15-30 seconds of waiting. That is actually monstrous and I hate it as much as you do, I cannot stand waiting for my app to rebuild, I turn to reddit, or YouTube. The more time waiting for feedback the less interactive and less enjoyable a programming experience is for me. So I think we're on the same page on that.
On the other hand, if you're just using normal Haskell code with reasonably modularized file structure, GHC will be able to rebuild incrementally and link just that module quickly. E.g. if it's a web app, you can have MyProject.View.Person and just change something in a blaze-html template and hit F12 in your editor (as I do) and it'll rebuild and restart in a second. I mean, to rebuild hpaste entirely from scratch takes 5 seconds:
$ time cabal build --ghc-options='-fforce-recomp -O0'
Compiling [snip]
Linking dist/build/hpaste/hpaste ...
real 0m5.116s
user 0m3.936s
sys 0m0.680s
Which is great. hpaste is only 4kloc, but your average codebase is between 3 and 15. In a normal development cycle you're changing just one or two modules at once, so with incremental compilation the refresh cycle is very fast. If you just want to type-check there is the -fno-code flag which brings it down to 1.2s to typecheck the whole project.Another approach I've taken with an IRC server (hulk) is running it inside GHCi. That works surprisingly well. Especially if you have your run function return the state as a mutable reference, then you can take a look at it while it's running and update it. Here's hpaste running in GHCi:
λ> :set args "hpaste.conf"
λ> tid <- forkIO main
Listening on http://0.0.0.0:1234/
PASS ******
USER hpaste * * *
NICK hpaste
λ> :t tid
tid :: ThreadId
λ> killThread tid
Shutting down...
λ> tid <- forkIO main
Listening on http://0.0.0.0:1234/
PASS ******
USER hpaste * * *
NICK hpaste
It's currently running on http://chrisdone.com:1234/ (I'll take it down later). Doesn't seem slow! Updating the code takes some milliseconds with :r and restarting is a case of killThread tid and fork again, some other milliseconds.So yeah, I feel you on dev time. I'm more inclined to the approaches that lead to more immediate development cycles, can't stand waiting. I'm Emacser/ex Common Lisper. GHC could be faster, but there are definitely circumstances which exacerbate its performance, I reckon, and ways to combat it (as above).
I know that's not a very good answer, more of a workaround than a reason. I don't know why GHC is particularly slow if it's a lot slower than Yhc was. Other than all its separate build steps, I get the impression from what people say that it's just mounted up and become quite hairy. And memory usage has never been much of a concern for the compiler. That's a pity and I'm all for work being done on improving its performance.
Funny how you demand specifics here, but "faithfully replicate the mistakes of php/mysql worst practices as interpreted by rails" is left oh so vague.
http://research.microsoft.com/en-us/um/people/simonpj/papers...
You might also take a look at GRIN. Here's a quote from the GRIN paper:
"For a lazy language like Haskell, compilers typically compile one module at a time. At first sight, this might appear as a good opportunity to optimize several procedures at once. However, it seems as if this does not apply very well to low level optimizations, like those presented in this paper, where the actual dynamic control flow is important... In a lazy language, a function that is local to a module in the source code might very well escape from the module at run time (if it is built into a closure) and then be called from somewhere else."
I'm very interested in your thoughts on frameworks, but this isn't really the right forum. It's usually pretty easy to catch me in #snapframework on IRC.