7 ms·
> that is altar upon which you sacrifice the abilitity I see statements like this all the time from people that either fundamentally misunderstand Haskell, and
by zoomerang 12y ago
> that is altar upon which you sacrifice the abilitity
I see statements like this all the time from people that either fundamentally misunderstand Haskell, and use to have the same misunderstandings myself. You really don't sacrifice anything by using it.
> the abilitity to write print statements to do debugging
I can slap a `trace` statement wherever the fuck I want inside my Haskell code for debugging. Even inside a pure function, no IO monad required. If I want to add a logger to my code, a 'Writer' monad is almost completely transparent, or I can cheat and use unsafePerformIO.
> and lose the ability to reason about order of execution.
If I'm writing pure code, then order of execution is irrelevant. It simply does not matter. If I'm writing impure code, then I encode order of execution by writing imperative (looking) code using do-notation, and it looks and works just like it would in any imperative language.
> But does it really result in more performant code
Haskell has really surprised me with its performance. I've only really been using it for a short time, having been on the Java bandwagon for a long time.
One example I had recently involved loading some data from disk, doing some transforms, and spitting out a summary. For shits and giggles, we wrote a few different implementations to compare.
Haskell won, even beating the reference 'C' implementation that we thought would have been the benchmark with which to measure everything else, and the Java version we thought we'd be using in production.
Turns out that laziness, immutability, and referential transparency really helped this particular case.
- Laziness meant that a naively written algorithm was able to stream the data from disk and process it concurrently without blocking. Other implementations had separate buffer and process steps (Even if hidden behind BufferedInputStream) that blocked the CPU while loading the next batch of data
- Immutability meant that the Haskell version could extract sections of the buffer for processing just by returning a new ByteString pointer. Other versions needed to copy the entire section into a new buffer, wasting CPU cycles, memory bandwidth, and cache locality.
- Referential transparency meant that we could trivially run this over multiple cores without additional work.
Naturally, a hand-crafted C version would almost certainly be faster than this - but it would have required a lot more effort and a more complex algorithm to do the same thing. (Explicit multi-threading, a non-standard string library, and a lot of juggling to keep the CPU fed with just the right amount of buffer).
On a per-effort basis, Haskell (From my minimal experience) seems to be one of the more performant languages I've ever used. (That is to say, for a given amount of time and effort, Haskell seems to punch well above its weight. At least for the few things I've used it for so far).
I'm still of the impression that well written C (or Java) will thoroughly trounce Haskell overall, but GHC will really surprise you sometimes.
I haven't used OCaml much - but my understanding is that the GIL makes it quite difficult to write performant multi-threaded code, something that Haskell makes almost effortless.
- cbd1984 12y ago> Haskell won, even beating the reference 'C' implementation This has always interested me. I have never gotten an answer, and I suppose I can't seriously expect one now, but I am still compelled to ask: Why did you put C in quotes up there? Why isn't Haskell in quotes? You didn't put C in quotes in other parts, but that isn't what I'm talking about.
- zoomerang 12y agoNo specific reason really. I didn't think about it at the time, that's just how I typed it. Probably because C is a single letter, and thus potentially needs some differentiation from the surrounding sentence, whereas Haskell is an actual word. But no idea really.
- cbd1984 12y agoThanks for your answer.
- iopq 12y agobecause 'C' is a char, while Haskell is a string
- marcosdumay 12y agoBut then there should be double quotes.
- millstone 12y ago> You really don't sacrifice anything by using it What's "it" - Haskell, or referential transparency? Referential transparency definitely has its victims, and debugging is one of them. Debug.Trace is quite useful, and also violates referential transparency. That Haskell provides it is an admission that strict R.T. is unworkable. > If I'm writing pure code, then order of execution is irrelevant. It simply does not matter. If I'm writing impure code, then I encode order of execution by writing imperative (looking) code using do-notation, and it looks and works just like it would in any imperative language.. Baloney! Haskell's laziness makes the order of execution highly counter-intuitive. Consider: import Data.Time.Clock main = do start <- getCurrentTime fact <- return $ product [1..50000] end <- getCurrentTime putStrLn $ "Computed product " ++ (show fact) ++ "in " ++ (show $ diffUTCTime end start) ++ " seconds" This program appears to time a computation of 50000 factorial, but in fact it will always output some absurdly short time. This is because the true order of execution diverges greatly from what the program specifies in the do-notation. This has nothing to do with purity; it's a consequence of laziness. > Turns out that laziness, immutability, and referential transparency really helped this particular case I don't buy it. In particular, laziness is almost always a performance loss, which is why a big part of optimizing Haskell programs is defeating laziness by inserting strictness annotations. > Laziness meant that a naively written algorithm was able to stream the data from disk and process it concurrently without blocking This would seem to imply that Haskell will "read ahead" from a file. Haskell does not do that. > Immutability meant that the Haskell version could extract sections of the buffer for processing just by returning a new ByteString pointer. Other versions needed to copy the entire section into a new buffer Haskell returns a new pointer to a buffer, while other versions need to copy into a new buffer? This is nonsense. Like laziness, immutability is almost always a performance loss. This is why ghc attempts to extract mutable values from immutable expressions, e.g. transform a recursive algorithm into an iterative algorithm that modifies an accumulator. This is also why tail recursive functions are faster than non-tail-recursive functions! > Referential transparency meant that we could trivially run this over multiple cores without additional work It is not especially difficult to write a referentially transparent function in C. Haskell gives you more confidence that you have done it right, but that measures correctness, not performance. Standard C knows nothing of threads, while Haskell has some nice tools to take advantage of multiple threads. So this is definitely a point for Haskell, compared to standard C. But introduce any modern threading support (like GCD, Intel's TBB, etc.), and then the comparison would have been more even. When it comes to parallelization, it's all about tuning. Haskell gets you part of the way there, but you need more control to achieve the maximum performance that your hardware is capable of. In that sense, Haskell is something like Matlab: a powerful prototyping tool, but you'll run into its limits.