11 ms·
My impression of Haskell is that the main issue is that laziness is simply wrong, since it causes space leaks that are hard to reason about, and seems generally
by devit 9y ago
My impression of Haskell is that the main issue is that laziness is simply wrong, since it causes space leaks that are hard to reason about, and seems generally less efficient than eagerness; furthermore, it seems rarely beneficial, so it seems more appropriate to explicitly request laziness rather than the opposite.
The next problem is that complete immutability is also wrong, because controlled mutable aliasing with linear types (like the system available in Rust) is what you really want since it's more general, efficient and expressive (allows mutable data structures), and complete immutability is just a special case of controlled mutable aliasing.
The third problem of Haskell is the weird syntax, that doesn't follow the common C/Java/JS or Python syntaxes for no good reason, making it hard to read and learn the language.
And if one were to change these things in Haskell, the result would essentially be Rust (or more precisely, a future version of Rust once all the missing abstractions like HKT, specialization, etc. are added), so I think that's what one should use instead.
- d3ckard 9y agoTotally disagree and as of now I only dabbled in Haskell (though it's first on my to-do list). 1. Immutability works when everything is immutable. Otherwise you're never sure and you lose benefits. 2. Syntax is a non issue. Honestly, I stopped caring a long time ago about how language looks. You get used to it in the first month. 3. Laziness is disputable. While it can be a optimization problem, it also allows for more generic description of ideas.
- devit 9y agoIn Rust, if you have an & or Rc/Arc, you know that it's immutable, except for parts contained in Cell/RefCell/Atomic/Mutex. If you have an &mut or owned mut variable, then you know that you can only mutate it through that name. If you have a non-mut owned variable, then you know that only the Cell/RefCell/Atomic/Mutex parts can be mutated, and only through that name. If a Rust program doesn't contain the words "mut", "unsafe", "Cell", "RefCell", "Mutex" or "Atomic[...]", then everything in it is immutable. That gives the same guarantees as Haskell's immutability where desired, but it allows to actually make use of CPU capabilities like writing to memory, and express basic operations like assigning a value to a position in an array. Or in other words, what you really want is a system that lets you mutate something as long as you are the only one with a reference to it, and then make it immutable and give it around, while also allowing you to "get it back" exclusively either with static checks (via lifetimes) or dynamic reference count checks. Haskell's pure immutability system, while safe unlike Java's, is just a very restricted version of such a system, that is not powerful enough to write safe programs that are as efficient as C programs (since you can't modify memory), or express imperative code even when they are safe.
- wyager 9y ago> except for parts contained in Cell/RefCell/Atomic/Mutex. This caveat is the problem. You lose a ton of properties when you have “interior mutability” (I.e. an object typed as immutable is, in fact, mutable). You probably can’t appreciate the benefit if you haven’t actually used a purely immutable language. Rust touts “fearless concurrency”, but it’s nowhere close to what you can get with Haskell’s guarantee. > not powerful enough to write safe programs that are as efficient as C programs (since you can't modify memory) Wrong on both counts. The vast majority of mutable algorithms can be expressed recursively and immutably. Consider haskell’s Vector library. It generates C-equivalent output a lot of the time despite being immutable and much higher level. The one exception I’ve run into is algorithms that inherently rely a lot on random array writes (like in-place random shuffles), and in this case Haskell has the ST monad for externally pure, referentially transparent mutability that cannot escape into the rest of your program. The fact that haskellers almost never need or want to use ST is very strong evidence that, in fact, you don’t really need mutable semantics at all once you’ve figured out what you’re doing with immutable data.
- devit 9y agoWell, you could change Rust so that the names of types with interior mutability have to start with an "@" or something like that, and then it would be obvious, and could expose a trait to denote that (there is Sync and the internal Freeze, but they don't quite do that). I think it hasn't been done because interior mutability is relatively rarely used, and being able to assert immutability in generic code is not actually that useful (and for things like hash map keys you'd need to implement Hash or other traits manually anyway, so you'll probably notice the issue). In non-generic code, you have to call functions like "borrow()", etc. to access the internally mutable data, so you know it's mutable. Regarding "fearless concurrency", the Sync trait ensures that, allowing "interior mutability" only if thread-safe (i.e. Mutex or Atomic). Obviously you can have race conditions between modifications of different Mutex/Atomics, but that's an unavoidable fundamental problem: even transactional systems will have race conditions if you use two separate transactions where you should have used one, and single-threaded event based systems also have them if you "defer" part of the computation improperly.
- 9y ago
- acdha 9y ago> 1. Immutability works when everything is immutable. Otherwise you're never sure and you lose benefits. That seems like a poor way to explain the concept since the entire point of programming is to mutate state and that's how the hardware actually works, too. I prefer to discuss it as tackling that uncertainty by allowing the language to indicate more nuanced contracts and enforce them, where immutability-by-default is a reasonable design which still gives you the option of using a mutable structure where necessary. > 2. Syntax is a non issue. Honestly, I stopped caring a long time ago about how language looks. You get used to it in the first month. I think this is a big mistake: if you want a language to be successful, that first experience is pretty important if you want big open source community with many contributors. In the case of Haskell, this is especially important since there are a number of other hard concepts which people need to learn along with the syntax and the nature of the language means that other programming experience is less helpful.
- AnimalMuppet 9y ago> That seems like a poor way to explain the concept since the entire point of programming is to mutate state and that's how the hardware actually works, too. The hardware actually works using gotos, too (well, jumps in assembly language). That does not make it either ergonomic or safe to write code that way. The entire point of programming is to compute results. In the hardware that results in mutations; in the programming model it doesn't necessarily have to.
- acdha 9y agoMy point was simply that saying “immutability works when everything is immutable” often sounds confusing and/or wrong because people know they need to mutate things. Saying that you're using a language with more advanced concepts to ensure that things only change in the desired manner avoids that initial point of confusion.
- lucozade 9y ago> laziness is simply wrong No it's not. It's not a particularly sensible default (which you allude to) but it's not wrong. Modelling delayed resolution is very handy. For example, you'd not get far in Rust without Result<>, Option<> or Future<> which are all make use of delayed resolution for their value. And the more we move to parallel models, the more important it will be. > because controlled mutable aliasing with linear types ... is what you really want Again, no. It's less restrictive than pure immutability, and Rust has shown that statically managing some mutability is viable, which is great, but it's not what you really want. What you really want is a static checker that only prevents shared, simultaneous mutability but admits everything else. The Rust borrow checker is an important step in that direction but it's not it. > The third problem of Haskell is the weird syntax I'm not one of life's Lisp apologists. Syntax is important, though not as important as semantics. But just because you find it difficult to read doesn't make it objectively difficult. And if you're implying that Rust is easy to read, for a beginner, because it uses curly braces, I'm not entirely sure what to say. I think Rust is an excellent language. I hope and believe it will be an influential language as it's a fantastic combination of innovation and pragmatism. I'm also enormously grateful that a lot of the decisions are made in the open, I've learnt a lot and continue so to do. But it's a good language because the designers clearly have an appreciation of what has gone before. That's a good way to be.
- devit 9y agoObviously a language needs to be capable of expressing laziness (which requires closures and some mutability), but it being the default is the issue in Haskell, since basically everything returns a closure computing the value rather than the value itself, which is really weird and unexpected, and causes unintuitive (at best) memory usage. As for implicit parallelization with no annotations at all, the main Haskell implementations don't seem to do it, so I guess it just doesn't work (the reasons probably being that there are penalties due to caches being CPU local and the cost of synchronizing threads that make automatic micro-parallelization much worse that explicit coarse-grained parallelization, unless the micro-parallelization is done in hardware by the CPU out-of-order logic). And obviously a type system with controlled mutability like Rust's allows to express explicit parallelization just fine (in fact, writing a safe explicitly parallelized web browser in Servo was one of the main goals of it). > What you really want is a static checker that only prevents shared, simultaneous mutability but admits everything else. The Rust borrow checker is an important step in that direction but it's not it. Is there any example of a static checker that allows more expressive programs than Rust's while preserving the same guarantees? (and without requiring extra annotations like programmer-supplied proofs or using best-effort automatic theorem proving). Is that even possible?
- deleted 9y ago[deleted]
- lmm 9y ago> My impression of Haskell is that the main issue is that laziness is simply wrong, since it causes space leaks that are hard to reason about, and seems generally less efficient than eagerness; furthermore, it seems rarely beneficial, so it seems more appropriate to explicitly request laziness rather than the opposite. Agreed; Idris is strict by default and I expect other post-Haskell languages will be too. > The next problem is that complete immutability is also wrong, because controlled mutable aliasing with linear types (like the system available in Rust) is what you really want since it's more general, efficient and expressive (allows mutable data structures), and complete immutability is just a special case of controlled mutable aliasing. Disagree. Mutability is so rarely what you want that it's better to have immutability by default, and a clunkier syntax for mutable is ok. > The third problem of Haskell is the weird syntax, that doesn't follow the common C/Java/JS or Python syntaxes for no good reason, making it hard to read and learn the language. Yes and no. Curried-by-default is the source of a lot of Haskell's power, and that requires a certain amount of unusual ordering. The terseness of pointfree style is worth some learning time. $ is a big ergonomic advantage, though I'm not entirely convinced it's worth the cost. There are a few cheap wins, some of which Idris has (e.g. single colon for type annotations, standardised syntax highlighting) but you don't want to throw away the good stuff. > And if one were to change these things in Haskell, the result would essentially be Rust (or more precisely, a future version of Rust once all the missing abstractions like HKT, specialization, etc. are added), so I think that's what one should use instead. Agreed, but that thing doesn't exist yet, and the Rust that exists today isn't an acceptable substitute (it's 2017, I shouldn't have to use a language without HKT). For the moment I'd say Idris is as close as you can get.
- frankpf 9y ago>Yes and no. Curried-by-default is the source of a lot of Haskell's power, and that requires a certain amount of unusual ordering. The terseness of pointfree style is worth some learning time. Correct me if I'm wrong, but you don't need Haskell's syntax to have automatically curried functions or a pointfree style. Using a JS-like syntax as an example: function add(a, b) { return a + b; } // functions could be automatically curried, // so you could do this: let add5 = add(5); add5(10) // => 15 // "Normal" sum definition function sum(list) { return reduce(add, 0, list); } // Pointfree version function sum() { // returns a function that takes // 1 argument (the list) return reduce(add, 0); }
- shmish111 9y ago"for no good reason"? It come from the ML family of languages, that's the reason, is it not as good a reason as why Java has it's syntax? It's quite easy to read once you get used to it, no more difficult than the languages you mention. Saying a particular paradigm is simply wrong seems like a very closed minded way of looking at things.
- MikkoFinell 9y agoIs it satire? Touting Rust while complaining that Haskell is hard to read? Please tell me you're joking.
- drb226 9y ago"The third problem of Haskell is the weird syntax, that doesn't follow the common C/Java/JS or Python syntaxes for no good reason" Haskell came before Java, JS, and Python. Perhaps it's their fault for choosing C-like syntax instead of Haskell-like syntax.