4 ms·
> But we could wish for a language with the efficiency and in-the-large maintainability of C++ and the clarity and generality of Python. What about Haskell? Ma
by tomsmeding 5y ago
> But we could wish for a language with the efficiency and in-the-large maintainability of C++ and the clarity and generality of Python.
What about Haskell? Maintainability yes; efficiency mostly. It has a GC that can be a pain for high-memory scenarios if you don't spend a lot of effort optimising, but on the whole Haskell is surprisingly fast for its expressivity.
Any odd? `any odd numbers`. Or equivalently, `or (map odd numbers)`, which is compositional in that `map odd numbers` is just a list again -- no distinction between lazy and strict lists in Haskell. Your other four examples are `and (map odd numbers)`, `zip numbers (map odd numbers)`, `sum (map (fromEnum . odd) numbers)`, `sum (zipWith (*) numbers (map (fromEnum . odd) numbers))`. Note I used `map odd` instead of defining `oddnesses = map odd` and using `oddnesses`, because the definition is shorter than the name. :)
This changes somewhat when you use arrays instead of lists: Haskell's lists are in terms of behaviour mostly "generators that are implicitly stored as a lazy linked list if necessary". So if you want to store stuff more compactly, or you want to index into the list (as opposed to looping over it), you need arrays. But even for arrays the story doesn't change much! Using the 'vector' library, actually all of the above examples work just fine, with no unnecessary intermediate vectors because of some intelligent fusion rules, if you put `V.` before the list-related function names.
- kragen 5y agoHaskell is #1 for generality but #0 for clarity, though I have to admit that some of my Python examples aren't exactly shining exemplars either. But if I write code in Haskell I can only call it from Haskell (which wasn't something I mentioned in my original comment, but is important). I think there's an underlying tension between generality and clarity. Clarity is when the code can't do things that aren't immediately obvious upon reading it. Generality is when it can. (There's a bit of squirrely equivocation here about what kinds of "thing" is being done, and what sense of "can" is meant, which is why they're just in tension and not really opposite, but I think there's some truth too.)
- tomsmeding 5y ago> But if I write code in Haskell I can only call it from Haskell (which wasn't something I mentioned in my original comment, but is important). Right, that's a downside. I believe there is a way in which you can call Haskell code from C, but I've never seen anyone use it myself. > I think there's an underlying tension between generality and clarity. You're on to something here. C++ has the same problem, where the syntax itself is context dependent: `a * b;` could be a multiplication if `a` is a variable, or a variable definition if `a` is a type. Also template meta-programming, and overloading and {con,des}tructors in general. (Of course, Haskell has the overloading thing even more -- though the containment of IO helps matters quite a bit in my opinion.) Golang explicitly chose to go the clarity way, and it got them some love and plenty of flak. Maybe one should just conclude from this that Haskell is not a great language for corporate environments. On the other hand, I think it's great for small-team development due to its expressivity and generality. But these two claims do not contradict each other: not every language is good for every purpose.
- Zababa 5y ago> on the whole Haskell is surprisingly fast for its expressivity I don't know if I would call it "surprisingly" fast. It is plenty fast, but it's a language that's AOT and these languages are usually quite fast, faster than most JIT'd and interpreted languages. Looking at the programming languages benchmark games and the techempower web framework benchmark, Haskell is around Java and Go. Which means that it can compete against the most advanced JIT and a reasonably good AOT, while being more expressive than both. That sounds reasonable to me. I may be underestimating what it takes to make something like Haskell fast though.
- kragen 5y agoI think it's surprisingly fast because it's usually much faster than things like SBCL, (last time I checked) Racket, and modern JS, and languages like Java and Golang are much less expressive.
- Zababa 5y agoI had SBCL around Haskell/Go/Java in my "mental model of language performance", but I don't know much about it and I'm not sure how to evaluate it. From what I understand, Racket uses a bytecode VM and/or a JIT, so Haskell being faster wouldn't surprise me. For Java and Go, that's true, though in both cases that's mostly because of decisions rather than tradeoffs. Go doesn't have a primitive type system because it makes it faster, but mostly because that's a design choice. Same thing for Java. I'd say you can get a good part of what makes Haskell good by using a ML as the basis for your language . Lack of ad-hoc polymorphism is usually a criticism of ML and its descendants, but Go and Java both lack it too, and both added generics 10 years after the language was created, which ML supports from the beginning. Rust seem to have achieved having parametric and ad-hoc polymorphism, but I don't know how easy or hard it would be to "extract" that "part" of Rust (without the borrow checker) and basically stick a GC on it. I also don't know how performant it would be. Lastly, Go, Java and OCaml all share fast compilation times, Haskell doesn't really and Rust is known for being slow. Again, I'm far from an expert on all of that, but I think Haskell in the right place in terms of performance and expressivity. It's one of these cases where designing a language for performance in the first place instead of retrofitting it makes things much easier, I think.