9 ms·
Haskell's rhetoric has always seemed a bit Pharisaical to me: "Though the runtime is impure, Our Code is uncontaminated by the taint of side effects".
by teyfille 12y ago
Haskell's rhetoric has always seemed a bit Pharisaical to me: "Though the runtime is impure, Our Code is uncontaminated by the taint of side effects".
- zoomerang 12y agoNot really. The point of Haskell is not to avoid having side effects. The point of Haskell is to allow code to be referentially transparent - this makes it both easier to reason about as a developer, and easier for the runtime to optimise.
- teyfille 12y agoYes, that is altar upon which you sacrifice the abilitity to write print statements to do debugging, and lose the ability to reason about order of execution. But does it really result in more performant code? Every benchmark I've ever seen, more practical languages like Ocaml have come out on top.
- fortknots 12y agoActually, Haskell does let you write print statements for debugging. If we have the following function: foo :: Int -> Int foo x = x `div` 0 and we want to add debugging, we can do: import Debug.Trace foo :: Int -> Int foo x | trace (show x) False = undefined | otherwise = x `div` 0 The above will print the value of x before throwing an error due to division by zero. You don't have to make foo return an IO Int or change any other aspect of your program.
- gamegoblin 12y agoSomething I like to do is: import Debug.Trace wtf v x = trace (show x) v someFunction x = anotherFunction x `wtf` x This allows me to tack `wtf` onto the end of otherwise unchanged expressions to do print debugging
- deleted 12y ago[deleted]
- thinkpad20 12y agoHaskell has a slight, but consistent, edge over Ocaml in most of the "benchmarks game" tests, so it's certainly not true to say that Ocaml beats Haskell in all benchmarks (although, certainly there could be other benchmarks where it does). In either case, Haskell is very performant and is competitive with any other mainstream language in performance. You can write print statements to do debugging (with Debug.Trace), and in practice it's not very hard to work IO into your code when you need it (even if only for temporary debugging or development). Crucially, however, it's much harder to accidentally work IO into your code. The few cases where I really miss print statements "for free" are vastly outweighed by the many cases in impure languages where I'm accidentally mismanaging my mutable state. Whether it results in more performant code? In some cases yes (the restrictions make it much easier to prove certain compiler optimizations), but that's not really the point. Referential transparency is about making your code more expressive, and easier to reason about, to design, and to safely tweak.
- yummyfajitas 12y agoHaskell performance is very good when written by people who know how the compiler works, and know the bytecode they want generated. I.e., if you rewrite a recursive function in a slightly unintuitive way and apply the right strictness annotations, it will compile down to the same bytecode as a for-loop in C. Idiomatic Haskell is not generally as fast as mutable C/Java/etc. Creating/evaluating thunks is not fast and immutable data structures often result in excess object creation. When you need them, there is no real substitute for unboxed mutable arrays, something Haskell does NOT make easy. Haskell is one of my favorite languages, the performance story just isn't quite what I want it to be. I do, however, think that there is plenty of room for improvement, i.e. there is no principled reason Haskell can't compete.
- fortknots 12y agoThis is exactly where I'm at. My biggest problem is that wrapping non-persistent data structures written in C/C++ never seems comes out right in Haskell. You often have to write them in the IO monad, which is the absolute last thing you want for an otherwise general purpose data structure. I think there may be some solution here using linear types, which enforce that a data type is referenced only once at compile time. This would let you avoid being forced to guarantee persistence when all you care about is speed. This argument may seem more abstract than what you mention, but in fact it gets to the very heart of why there aren't good unboxed mutable arrays in haskell. In truth, there are. You can convert Immutable Vectors (which are lists with O(1) indexing but no mutation) into Mutable Vectors in constant time using unsafeThaw. The problem is that your code is no longer persistent, and you've risked introducing subtle errors. My biggest problem is that the haskell community seems to look at non-persistent data structures as sacrilegious. As a scientific programmer, that makes me feel like maybe learning haskell wasn't such a good investment after all. But on the bright side, functional programming is on the rise, and I'm confident that all my experience with Haskell will transfer well in the future.
- 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.
- imanaccount247 12y ago>sacrifice the abilitity to write print statements to do debugging No. >lose the ability to reason about order of execution No. >But does it really result in more performant code? That is not the goal. The goal is being able to reason about the code, and write code that is correct. The fact that it performs very well is due to a high quality compiler, not purity. > Every benchmark I've ever seen, more practical languages like Ocaml have come out on top. Doesn't look that way from here: http://benchmarksgame.alioth.debian.org/u32/ocaml.php http://benchmarksgame.alioth.debian.org/u32/ocaml.php How exactly is a language that is unable to handle parallelism "more practical" than one that handles it better than virtually any other language?
- methodology 12y agoOCaml is more practical than ML and Haskell because it has objects, for loops, more edge cases in the language, built in mutable keyword, and extensible records.
- imanaccount247 12y agoNo it is not. Ocaml's objects make it less practical, not more. That is why they are virtually completely unused. At best, for loops are irrelevant. I'd say they are closer to a negative than irrelevant though. What do you mean "more edge cases?" That the language is less safe? How is that practical? Haskell has mutable references too, with the added benefit of them being type safe. And haskell has extensible records, they are just a library like anything else: http://hackage.haskell.org/package/vinyl http://hackage.haskell.org/package/vinyl
- methodology 12y ago> That the language is less safe? Not necessarily. > And haskell has extensible records, they are just a library like anything else: And OCaml has monads, they are just a library like else.
- dllthomas 12y ago
- tel 12y agoPerhaps the best way to think of it is that a Haskell program is a pure function from FFI outputs to FFI inputs. Something like H out in = out -> (FFIOperation, in) The `IO` monad is nothing more than how operating "inside of this function" feels. The runtime thus operates the pure Haskell program evaluating its FFI demands and returning their results. The AST POV espoused by this article is quite good as well, but a little bit less obvious how to "step" things forward or operate nicely in parallel contexts. Also, in reality the above perspective is a good way to embed Haskell into other contexts. http://comonad.com/reader/2011/free-monads-for-less-3/ http://comonad.com/reader/2011/free-monads-for-less-3/
- skybrian 12y agoThe way I think of it is that an IO value is a program where all the Haskell code runs in callbacks. This is a bit like a JavaScript program where everything runs in a callback. The major difference is that callbacks written in Haskell are constrained to be pure functions. The IO type is rather rigid in that there is always a next callback, which implicitly contains the entire program state as part of the function closure. In a reactive programming language like Elm you can have many functions that may be the next callback depending on what the next event is, along with all the callbacks that run as part of the signal graph. Purity is about how constrained the callbacks are, not about the overall structure of the program.
- dustingetz 12y agoPhysics is pure, god is the runtime.
- serve_yay 12y agoI don't know what that word means, to me it has always seemed a bit silly. The whole point of software is its side effects, you will always have some. So instead of admitting that, you do a little dance to pretend side effects don't really happen in your program. It's very strange to me.
- outworlder 12y agoThe same way you pretend to have infinite memory by using a garbage collector.
- dllthomas 12y agoOn the contrary, the whole point of software is its effects. They are only side effects if you're not able to encode the effects in the type of the function. Haskell doesn't treat effects as less important, it treats them as more important by allowing (and, as a consequence, forcing) you to reason about them explicitly in compiler-checked ways. This lets you do great things like have STM transactions with a guarantee that retrying them won't screw anything up. It also lets you define your own kinds of effects that you want to track and plumb them through your system in a coherent way. And it lets you write code that doesn't care what kind of effects it's working with, and yet which can still be used in contexts like STM that need to apply restrictions.
- marcosdumay 12y agoWhat I'm not liking about this line of reasoning is that in practice Haskell seems to not go far enough[1]. Yes, you have to declare your effects. In practice that means that most of your code returns IO, and isn't constrained anymore. I don't know if this is a library feature, or an essential feature of the language[2], but it would be very interesting for example to put a GUI together by computing events in functions that returned an "Event" monad, widgets in functions that returned a "GUI" monad, database access in functions in a "DB" monad, etc. Instead, all of those operate on IO. [1] A completely subjective assessment. [2] I've though for a short while on how to code that, but didn't got any idea I liked.
- quadrangle 12y agoAre you sure that's quite what Haskell's rhetoric is? It sounds to me more like the bad rhetoric of people who do a poor but enthusiastic job of trying to explain Haskell.