Table of Contents for Programming Languages: a survey
Haskell
Because it is so well-liked, Haskell gets its own chapter.
"In Haskell’s case, the Big Idea was purely functional lazy evaluation (or, if you want to be pedantic, call it “non-strict” instead of lazy)." [1]
Attributes:
Pros:
- purity
- expressive type system
- lazy evaluation
- type classes
- monads
- lightweight threads:
- thread scheduler can handle millions of threads [2]
- each thread requires 1 kb of memory [3]
- Haskell channel overhead for the standard library (using TQueue) is on the order of one microsecond per message and degrades linearly with increasing contention [4]
- Haskell channel overhead using the unagi-chan library is on the order of 100 nanoseconds (even under contention) [5]
Syntax:
Library highlights:
Extension recommendations:
The following extensions are within the top 18 items and are >= net 33% in the 2020 Haskell survey poll, and are in the top 22 items and are >= 15% in the 2019 Haskell survey poll, and are rated as Basic in https://limperg.de/ghc-extensions/, and are rated as Benign or General in http://dev.stephendiehl.com/hask/#language-extensions, and are rated as 'Use at will' on https://gist.github.com/mightybyte/6c469c125eb50e0c2ebf4ae26b5adfff , and are on the list of default extensions in https://lexi-lambda.github.io/blog/2018/02/10/an-opinionated-guide-to-haskell-in-2018/ , and are in at least one of https://www.schoolofhaskell.com/user/PthariensFlame/guide-to-ghc-extensions, https://ocharles.org.uk/pages/2014-12-01-24-days-of-ghc-extensions.html:
- DeriveFoldable?
- DeriveFunctor?
- DeriveGeneric?
- DeriveTraversable?
- FlexibleContexts?
- FlexibleInstances?
- GeneralizedNewtypeDeriving?
- LambdaCase?
- MultiParamTypeClasses?
- OverloadedStrings?
- ScopedTypeVariables?
- TupleSections?
The following extensions are within the top 18 items and are >= net 33% in the 2020 Haskell survey poll, and are in the top 22 items and are >= 15% in the 2019 Haskell survey poll, and are rated as Basic or Advanced in in https://limperg.de/ghc-extensions/, and are rated as Benign or General or Advanced or Lowlevel in http://dev.stephendiehl.com/hask/#language-extensions, and are rated as 'Use at will' or "More complex, but well understood" on https://gist.github.com/mightybyte/6c469c125eb50e0c2ebf4ae26b5adfff , and are on the list of default extensions in https://lexi-lambda.github.io/blog/2018/02/10/an-opinionated-guide-to-haskell-in-2018/ , and are in at least one of https://www.schoolofhaskell.com/user/PthariensFlame/guide-to-ghc-extensions, https://ocharles.org.uk/pages/2014-12-01-24-days-of-ghc-extensions.html except as noted:
The union of the previous two lists is 17 items. The only extension in the top 18 items or with >= net 33% of the 2020 Haskell survey poll but not on either of these two lists is DerivingVia?.
Here's another site that makes recommendations:
https://github.com/commercialhaskell/rio#language-extensions
This list contains all of the first 12 extensions noted above, and all of the second 5 extensions noted above, except for DerivingStrategies? and TypeApplications?; and this list also has many others not on either of those lists.
Another list is GHC's GHC2021;
This list contains all of the first 12 extensions noted above except for LambdaCase? and OverloadedStrings?, and all of the second 5 extensions noted above, except for DerivingStrategies? and GADTs (but it has GADTSyntax, what is the relation between these?), and this list also has many others not on either of those lists.
GHC has an older list, glasgow-exts. glasgow-exts contains all of the first 12 extensions noted above except for LambdaCase? and OverloadedStrings? and TupleSections?, and out of the second 5 extensions noted above it only contains RankNTypes?.
According to [8], Standard Chartered's strict Haskell variant Mu has MultiParamTypeClasses?, FunctionalDependencies?, PolyKinds?, DataKinds?, TypeFamilies? (only closed!), among others, and does not have ExistentialQuantification?, Rank2Types (and higher ranks), GADTs, CPP, TemplateHaskell?.
More data on Haskell language extensions at Standard Chartered from those presentation slides:
" In our 1,794 Haskell modules, we use 435 : DeriveDataTypeable? 410 : RecordWildCards? 284 : CPP 275 : OverloadedStrings? 187 : ScopedTypeVariables? 115 : MultiParamTypeClasses? 110 : FlexibleInstances? 89 : GeneralizedNewtypeDeriving? 77 : PatternGuards? 66 : FlexibleContexts? 59 : ViewPatterns? 59 : TypeSynonymInstances? 58 : TypeOperators? 51 : UndecidableInstances? 46 : StandaloneDeriving? 44 : DataKinds? 35 : TemplateHaskell? 32 : NamedFieldPuns? 31 : ExistentialQuantification? 30 : TypeFamilies? 30 : DeriveGeneric? 29 : ForeignFunctionInterface? 21 : TupleSections? 21 : FunctionalDependencies?
In our ~26,000 Mu modules, we use 7975 : RecordWildCards? 2342 : MultiParamTypeClasses? 1812 : DataKinds? 1527 : ViewPatterns? 975 : Recursion 787 : LambdaCase? 651 : GeneralizedNewtypeDeriving? 552 : NamedFieldPuns? 359 : KindSignatures? 255 : TypeFamilies? 252 : OverloadedLabels? 195 : PartialTypeSignatures? 188 : DeriveDataTypeable? 178 : FunctionalDependencies? 72 : PolyKinds? 66 : DeriveDefault? 60 : SignatureSections? 52 : TypeOperators? 44 : OverloadedStrings? 18 : FlexibleContexts? 17 : ScopedTypeVariables? 17 : DuplicateRecordFields? 13 : OverloadedRecordFields? 13 : ForeignFunctionInterface? "
Retrospectives:
Best practices, libraries, tooling:
Respected exemplar code:
Popularity:
- Beloved by those who use it, but not popular
Comparisons:
- Q: " In what ways is OCaml's pattern matching superior to Haskell's? " A: "Or patterns. See http://stackoverflow.com/questions/24700762/or-patterns-in-haskell" -- https://news.ycombinator.com/item?id=9701347
- "On the flipside (of OCaml having OR-patterns and Haskell not), Haskell just got pattern synonyms which are incredibly useful for being able to refactor and work with abstract types. It also has view patterns which are quite useful. I don't know how to replicate that in OCaml. Combining the two lets types expose fairly sophisticated interfaces as normal patterns, which is incredibly useful for things like graphs." -- https://news.ycombinator.com/item?id=9701476
- comparing error handling in Go to Haskell's either monad
- https://www.reddit.com/r/ocaml/comments/3ifwe9/what_are_ocamlers_critiques_of_haskell/
- https://www.reddit.com/r/haskell/comments/3huexy/what_are_haskellers_critiques_of_f_and_ocaml/
- https://www.reddit.com/r/haskell/comments/3gtbzx/what_are_haskellers_critiques_of_clojure/
- https://www.reddit.com/r/Clojure/comments/3h4qdk/what_are_clojurians_critiques_of_haskell/
- https://www.reddit.com/r/haskell/comments/3h7fqr/what_are_haskellers_critiques_of_scala/
- "I'm just starting with Haskell and PureScript?. So far I'm liking the latter better. It solves a few of their gripes with respect to strings, laziness and records, plus has a more granular/extendable effects system and cleans up the standard typeclass hierarchy. Also `head []` doesn't crash. Of course Haskell is more mature, has support for multithreading and STM, compiles to native, so it's more performant. But PureScript? integrates with JS libraries and seems "fast enough" in many cases. I think it's more interesting as a project too: the lack of runtime and laziness means the compiler code is orders of magnitude simpler than Haskell's, so I could see a larger community building around it if it catches on." daxfohl reply
- http://xion.io/post/programming/rust-into-haskell.html
- http://thume.ca/2019/04/29/comparing-compilers-in-rust-haskell-c-and-python/
- vs ML: "From one view, ML is the worse language, but from other views it would be Haskell. Suppose you require a fully formal specification which has been verified in a mechanized way. You have that for Standard ML in Twelf and Haskell has nothing sort of that. If you look at the concept of being a purely functional language however, it is the opposite with Standard ML being the "worse" animal. Module system: Then SML got it right and Haskell got it Wrong. Laziness/Strictness: This is a duality. There are advantages and disadvantages to both approaches so there is no worse/right choice IMO. If you look at the recent stuff on polarity in proof theory it becomes clear that when you latch onto a specific evaluation order, you make some things simple and other things hard." [13]
- vs OCaml:
- "language extensions fragment the language even within a single implementation. Especially when a lot of the extensions are just pointless syntactic sugar."
- "No labels or polymorphic variants which hurts readability."
- "String type (I'm not saying we've done much better here in OCaml)"
- "Records in OCaml are better"
- -- [14]
- see also the followup comment [15]
- https://wiki.haskell.org/OCaml
- vs OCaml: "I've used both languages a lot. My current job is in Haskell and my previous (along with two internships) was in OCaml. With that experience, I would use Haskell over OCaml for basically everything. If you're curious about details, I wrote an in-depth post about it a few years back. Core points:
- Haskell is massively more expressive in practice. The OCaml code I worked on was routinely more verbose and less flexible than the Haskell version would have been. Typeclasses and higher-kinded types make an immense difference, powering everything from lens to QuickCheck? to mtl...
- Haskell's parallelism and concurrency story is incredible. OCaml's... isn't. I've used both LWT and Async in OCaml and while they get the job done most of the time (unless you're computaiton-bound), they're not nearly as powerful or convenient as Haskell's threads and high-level abstractions like STM.
- I find Haskell's libraries to be much stronger than OCaml's overall. A big part of this is that the language is expressive enough to support some really high-level, general abstractions that would be too awkward to use in OCaml.
- Controlled effects (IO, ST... etc) are a revolution for software maintainability. I would hate going back to potentially impure code everywhere.
- At the end of the day there are a couple of things I really miss from OCaml (polymorphic variants and a record system that doesn't suck). On the other hand, I missed a ton of things from Haskell back when I was doing OCaml." -- [16]
- vs Ocaml: "Since someone ought to argue the other side and I'm a huge ML shill, I'll write my thoughts.
- Strict evaluation, while sometimes more cumbersome, is much easier to reason about from a performance perspective. You will never have to deal with a mysterious space leak because the code behaves predictably and the cost model is composable.
- OCaml has a far more sophisticated module system. In particular, OCaml modules actually have types, like everything else in the language! Better yet, OCaml even lets you write functions at the module level. related reading
- ML lets you encapsulate benign effects. To be fair, this is a double edged sword because the onus is on the programmer to encapsulate effects appropriately, but on the other hand if you need to use (e.g.) memoization you can do so without forcing the client code to pay attention to that implementation detail.
- Exceptional control flow is a pleasure to use in ML.
- Polymorphic variants are killer for expressibility.
- Concessions:
- The ecosystem is not as developed.
- The community is less active.
- Type-classes are really nice, and OCaml doesn't have them. (Although some fine day we won't have to sacrifice modularity to use type classes)" -- [17]
- vs Ocaml: "OCaml has its good sides, too. To add another one: it is (or at least feels like) a much smaller and simpler language than Haskell. The compiler is also simpler and easier to understand, if you want to start hacking on it. Recently, I'm especially impressed by BuckleScript? that translates OCaml into readable and relatively idiomatic JavaScript?."
- vs OCaml: "What do you value most: the ability to reason about correctness, from looking at the code, or the ability to reason about run-time characteristics (speed, memory usage)? In case of the former, I would recommend Haskell, and for the latter OCaml. Purity makes it much easier to reason about correctness, from looking at the code alone, since nothing is hidden from view. Laziness, however, makes it difficult to figure out how a piece of code will perform at run-time, without actually running it." -- [18]
- vs OCaml: "Are you working primarily on Windows? The Haskell tools (compiler, package system) work well on Windows, while OCaml not so much. On Mac/Linux though, OCaml tooling works great--but so does Haskell. From the language perspective, with Haskell you get enforced purity at the cost of having to deal with effect management like e.g. using a monad transformer stack. In OCaml you don't get the safety net of enforced purity but on the other hand you can just do side effects--I/O, mutation, etc.--wherever you want. Oh, and possibly the biggest difference--Haskell is lazily evaluated so you need to keep an eye on memory usage in large computations, but in return you get the performance benefits of lazy semantics--e.g. take 5 [1..] quickly gives you the first five numbers without trying to calculate an infinite list starting from 1." -- [19]
- vs OCaml: "I am using both languages. Syntax-wise I definitely prefer Haskell, it feels so much lighter---but I admit that OCaml has fewer syntax elements to learn."
- vs Go: "goroutines is way easier to use than forkIO & MVar and others. Way easier to write a performant code." [20]
- vs Ocaml: "For example, I could install a package where the latest commit was 5-8 years ago and it worked straight out of the box. That’s something unimaginable with Haskell."
- "the features that are prominent in Haskell that ended up going to Rust
Typeclasses (in Rust they are Traits)
Sum types (you may take this for granted, but a lot of languages don’t have them….)
Pervasive pattern matching
Hindley-Mindler type inference (automatic type inference for variables)
Pervasiveness of things being expressions rather than statements
Parametric Polymorphism
The feeling that once your program compiles, it will run" -- [21]
Opinions
- "Haskell is a great language but it isn't going to become a mainstream language any time soon (or ever)." -- [23]
- State of the Haskell ecosystem August 2015 by Gabriel Gonzalez
- "'A Haskeller tried convincing me that such a language is a good fit for human programmers, too, because all it is is a subset of more traditional, impure languages. But the notion of subset => simpler is a mathematical notion with no relevance to cognition. If humans operated in this way, natural languages would have been much more mathematically simpler, too.' I totally agree on this point and I've tried my hand at Haskell several times only to find myself going to some other language like F#, OCaml, or Common Lisp. It just feels too 'stiff' in terms of a programming language. I can't seem to write in it without running into its limitations (Not strictness. I can handle strictness in a language.). When I write a program I think about what I have to pull data from and what the data should become along the way. Even if parts of my program will be standardized into a library for a project I still like being able to get it setup in such a way that I can modify it easily. Haskell doesn't seem to let me do that without redoing the entire function signatures and sometimes due to the notation the signatures don't fit the data well if at all. " -- [24]
- "One technical reason that makes me prefer OCaml to Haskell is Haskell's weak module system. I think there should be a good way to parameterize one module over another module (interface) and to use different instantiations of the module in the same program. I think people do such things using type classes and typing constraints. But I found this being awkward, because the compiler needs to be able to resolve the constraints automatically and there are issues with overlapping instances and the like." -- [25]
- "...problem with all of these languages is that they are still obscure compared to everything else, with all that this entails: It's harder to find developers...Haskell is also a language I like a lot — at least in theory. I've been disappointed with performance and memory usage (and the ability to reason about those things). The tools for concurrency (and OTP-type distributed networking) also seem very much lacking. Whenever I see an example of parallel async code in Haskell, I feel like I'm reading an advanced physics paper. While I like and understand the approach to purity, I feel like the tools get in the way of programming; that I have to work at getting in and out of effectful code just so that I can stay pure, and for what reason? Bugs caused by side effects is something I encounter only a few times a year, if that; Haskell builds a huge wall to solve something that isn't a problem for me. Nor do I enjoy that laziness is the default. Finally, there are parts of the language that seem immature; the well-known data record namespacing issue (being fixed?), and the sheer awkwardness that comes with trying to implement certain things (like polymorphic collections and directed graphs) that should be simple, but aren't. Still, it's a beautiful language, but not one I would use for everything." -- [26]
- "I consider Haskell a beautiful language, but the most challenging for me to reason about and understand." -- [27]
- "Haskell has become a laboratory and playground for advanced type hackery" -- Simon Peyton Jones: Wearing the hair shirt: a retrospective on Haskell. POPL 2003, invited talk.
- "Haskell distinguishes itself from most languages by purity, laziness, and a strong, static, inferred type system. Purity lets you reason more easily about your program. Laziness allows for better function composition and optimisation (at the cost of harder-to-understand memory profiles). Strong, static, inferred types deliver a lightweight mechanism for enforcing consistency3, making refactoring a joy and eliminating a large class of bugs without the need to write and maintain tests...I think it is hard to get a Haskell codebase to a point where refactorings and improvements become scary....Haskell’s laziness requires you to think about space complexity more than in other languages. There are good tools and techniques to help with that and you can certainly write space-efficient code, but it takes more conscious effort than with strict languages. You tend to more than reclaim that mental load by having better and more powerful abstractions at hand though. There are also a few other annoyances like converting between string types, the absence of a standard style, and the lack of a good record syntax. Lenses do solve the record problem, but they take time to learn8 and are tricky to debug. In day-to-day use, Haskell is otherwise surprisingly pragmatic...Haskell is not for everyone. It is different from what most developers are used to. It requires learning abstract concepts. Its eco-system is not as mature as some more popular languages. It is rarely the quickest language for hacking together a 2-day prototype. It has garbage collection so it is unsuitable for real-time systems. Its space-complexity can be fickle. And the number of people with production experience of Haskell is small...Haskell is an outstanding language for building and rapidly iterating on high-quality software in small, skilled, and growing teams. As your codebase expands and evolves, Haskell’s ability to handle complexity, clarify thinking, and ensure consistency is a blessing. " [28]
- "broken typeclasses, lacking module system, poor tooling, and a weekly-changing, terrible dependency management" [29]
- "A few common complexity complaints i hear about Haskell that don't apply to purescript in order of frequency:
- - Haskell is lazy by default
- - has too many language extensions
- - records pollute global namespace
- - has fmap and map
- "I never deal with the fmap/map problem because I use ClassyPrelude?, and there are other even more trivial solutions out there (to the extent that that's a problem). Also, <$> is a thing." -- [30]
- - $ is confusing" -- [31]
- "Haskell sometimes feels like C++" [32]
- "The easy is hard, the hard is easy. For minor tasks (converting between two file formats, for example), I will not use Haskell; I’ll do it Python...However, in Haskell, it is trivial to add some multithreading capability to a piece of code with complete assurance of correctness."
- "Haskell is a great programming language." [33]
- "Performance is hard to figure out. Haskell and GHC generally let me get good performance, but it is not always trivial to figure out a priori which code will run faster and in less memory....in many languages you can look at two pieces of code and reasonably guess which one will be faster, at least within an order of magnitude. Not so with Haskell. Even very smart people struggle with very simple examples. This is because the most generic implementation of the code tends to be very inefficient. However, GHC can be very smart and make your software very fast. This works 90% of the time, but sometimes you write code that does not trigger all the right optimizations and your function suddenly becomes 1,000x slower." [34]
- "Every language has quirks about how to write performant code, but Haskell is notorious in my mind for being quite easy to write obscenely slow code in." [35]
- http://matt.might.net/articles/best-programming-languages/#haskell
- "The main problem I faced with Haskell when I tried to step aside from writing toy projects and get something production-ready was (lack of) documentation. Resources like Programming Haskell or What I wish I knew when learning Haskell are great but not enough, unfortunately. Something like modern version of Real World Haskell would've been immensely helpful...Haskell’s laziness may be tricky but the excellent built-in profiling tools remedy it, albeit at the cost of longer development time. More importantly though is that the library ecosystem leaves a lot to be desired and coupled with the tendency of having stumbling blocks where shouldn’t really be any it turned out to be the worst tool for that particular job. On the other hand it was the most powerful language I ever used and if you have time/resources to build a set of domain-specific, optimised libraries it seems to be capable of solving anything you can throw at it." [36] and [37]
- "...Haskell syntax is a mess (take a look at the grammar), there are a lot of edge cases (e.g. weird indentation rules)."
- "...learning Haskell well enough to be productive takes weeks if not months. As for Haskell, it sure addresses parallelism, but it introduces far more significant problems with respect to application development. There are reasons it isn’t widely used in production." [38]
- I adore Haskell, but my personal problems with it:
- Records! Lenses are fine, but a huge complexity increase. I love the recently accepted record syntax RFC though, this will make things a lot nicer.
- I feel like the plethora of (partially incompatible) extensions make the language very complicated and messy. There is no single Haskell. Each file can be GHC Haskell with OverloadedStrings? or GADTs or ....
- Library ecosystem: often I didn't find libraries I needed. Or they were abandoned, or had no documentation whatsoever, or used some fancy dependency I didn't understand. Or all of the above...
- Complexity. I can deal with monads, but some parts of the ecosystem get much more type-theory heavy than that. Rust is close enough to common programming idioms that most of it can be understood fairly quickly
- Build system ( Cabal, Stackage, Nix packages, ... ? ), tooling (IDE support etc) " [39]
- "A good example of a language feature that would not work in a language with a module system would be Haskell’s type classes, which for those who are not familiar with them, are very similar to Rust’s trait system. It’s the same thing. These are basically systems for doing ad-hoc polymorphism...I would say that type classes were a major breakthrough in ad-hoc polymorphism, and they’re still kind of the gold standard to measure your ad-hoc polymorphism features against, but it’s a very kind of, anti-modular feature. So, let’s say, you want an addition function and so you define an addition type class, and then you decide that you want it to work on integers, so you have to, somewhere in your program, write “this is how addition works on integers,” and the important thing there is that it really has to be, just in the entire program, for type class to work, there has to be just one place where you’ve written, this is how addition works on integers. And so, if you have one library that’s written an addition instance over here, and another one that written another addition instance, you will not be able to link them together. And it’s kind of anti-modular, because you don’t notice this problem until you get to link time. Like you try to link them together and it says, “Oh, no, those two bits have to live in separate worlds. They can’t combine like this. You have kind of broken some of the compositionality of your language.”" -- Leo White
- "I remember being actually quite surprised about this when I first heard about it, because there’s essentially no way of hiding this aspect of your implementation. You cannot in any way, wrap it up such that the particular choices you made about type classes in the middle of your program, aren’t exposed. It’s sort of antimodular in a fairly in your face way." -- Ron Minsky
- "Yeah. Like you are very clearly leaking details of your implementation...I did some work on the language feature called modular implicits, which is kind of based on some mix of type classes and a feature in Scala called implicits, and this is basically a direct attempt to solve, like the question of how do you make type classes work with modules, and you basically, you remove that restriction to only have one of them and you deal with the problem that that solves in a different way. To my mind, the result is a cleaner system – that is certainly subjective and debatable. " -- Leo White
- vs OCaml: " The Haskell community, in my experience, is far more academic. A recent post to the Haskell libraries mailing list began with: "It was pointed out to me in a private communication that the tuple function \x->(x,x) is actually a special case of a diagonalization for biapplicative and some related structures monadicially." It received 39 pretty enthusiast replies. I don't expect this sort of discussion on Ocaml mailing lists (please correct me, Ocaml mailing list subscribers!) And I think the reason is that Haskell makes this sort of abstraction very very easy, and makes it pay off, while Ocaml does not. In Ocaml, it's not even useful to abstractly define functors, which are the most basic data of category theory. In Haskell, functors are a typeclass that comes with the prelude and are hard to avoid. And there's no reason why you wouldn't use a biapplicative either: https://hackage.haskell.org/package/bifunctors-5.5.7/docs/Data-Biapplicative.html" https://mail.haskell.org/pipermail/libraries/2020-September/030789.html -- [40]
- (2016) Low latency, large working set, and GHC’s garbage collector: pick two of three. Describes how GHC's garbage collector is good for throughput but not for low latency
- "I think the problem here isn't power or expressiveness per se, but a large number of fundamental concepts. For example, vanilla Haskell has a lot of expressiveness built around a few core concepts (first-class functions, parametric polymorphism, typeclasses). However Haskell + 101 extensions or Scala derive a lot of their expressiveness from a plurality of fundamental languages features. That is what creates the real problem." [41]
- "...I would add that, after stack, tooling is not a problem anymore. It's not as good and polished as rust's cargo but it's ahead several other languages. Still, as a language, Haskell is not ideal for teaching and productivity. There too many different ways of doing things (eg. strings, records); compiler errors need improvement, prelude has too many exceptions-throwing functions (eg. head); exceptions handling is not something I want in a pure language; ghc extensions change the language so much that using some extensions almost feel like having to learn another language. On documentation, I can't say I feel the need for it, but I understand some developers may be used to program against documentation and feel lost without it. I think that Haskell is a great language to prototype pure business logic because of the type system and focus on purity, but it has several warts, because haskellers focus more on language research than DX. The reason I stopped using Haskell is because I was bit by exception handling (which is a feature shared by many other languages, incidentally!) and by GC spikes. I still like Haskell, it's closest to my "ideal" language than any other, but for production Rust is more useable (albeit being a bit uglier) " -- [42]
- " > Haskell is a language for experimenting, success is not their goal and it would remove resources to actual research. You rightfully point to Rust, which took a lot of inspiration from Haskell, but I think it's worth emphasizing just how much of the progress in programming languages in the last two decades was inspired by functional programming (many features were not invented in Haskell, but some like type inference were popularized by it). For example: proper type inference, algebraic data types (enums in Rust) and consequently option types, pattern matching, property-based testing, immutability by default, parametric polymorphism (generics), ad-hoc polymorphism (type classes/traits/...), first class functions (very old idea but only recently common in mainstream languages), ... " [43]
- " The Rust docs claim most things actually came from ML and OCaml: https://doc.rust-lang.org/reference/influences.html They only mention Haskell's type classes and type families as inspiring a Rust feature directly (traits). " " [44]
- " It's kind of alluded to, but, while FP languages, as a broad category, were the progenitor of a lot of different PL ideas, Haskell ended up implementing many of them in a single place. It's fair to say "we were influenced by (this other language that originated an idea)", but it's also fair "and Haskell also has that feature". Do it enough times, and you start to see why the claim that Haskell is important; being a research language it's able to have all of these ideas implemented in it in pursuit of its design goals, where other FP languages pick and choose in pursuit of a different one. " [45]
- "The haskell ecosystem is missing a certain kind of pragmatism. There's a lot of beautiful type abstractions, talking about monads, etc., but not enough builders doing actual application development. In my opinion it's not the language that is bad, but the ecosystem. Missing documentation, missing tooling and infrastructure, no focus on actually building applications." [46]
- https://www.reddit.com/r/haskell/comments/glz389/examples_of_incorrect_abstractions_in_other/
- a look into how the complexity of the Haskell type system can lead a programmer down a rabbit hole of generalization: https://blog.plover.com/2018/09/03/#what-goes-wrong https://blog.plover.com/prog/haskell/what-goes-wrong-2.html https://blog.plover.com/prog/haskell/what-goes-wrong-3.html
- https://lobste.rs/s/anog8g/haskell2020_is_dead_all_hope_is_not_lost
- https://medium.com/pragmatic-programmers/the-unreasonable-effectiveness-of-haskell-48d92c2fe266
- "Sadly, Haskell does not support multi-line string literals out of the box. You have to use a package like `here` along with TemplateHaskell? or QuasiQuotes? extension to get this basic feature" -- [47]
- "I love Haskell, but it's decades old, and one struggles to work with it without being torn apart by the gravitational tides of the black hole at the center of its galaxy. All jokes aside, Haskell really does reward a PhD? in category theory, or the self-taught equivalent later. Functional languages make parallelism easier, and Haskell has put particular effort into this: Adding a dozen lines to a program can keep the 16 performance cores of my Mac Studio running full tilt. I have yet to read any account of how someone else's favorite language makes parallelism easy, that is aware of the comparison with Haskell. If I thought there was a reasonable alternative, I'd jump at trying it." -- [48]
- "Haskell suffers from... QoI? issues (fragmentation due to language pragmas, opaque behavior due to laziness, forces the programmer on the path of programming with categories rather than programming with values, build/packaging issues)" [49]
- https://serokell.io/blog/haskell-in-production
- "I feel robustness is a bit hyped. If I want formal guarantees, something like Agda or Dafny is the way to go. Liquid Haskell does not feel nearly as mature. Using OCaml or Erlang, I feel I can write large programs that are reasonably correct more quickly." -- [50]
- https://journal.infinitenegativeutility.com/leaving-haskell-behind
- vs Ocaml and Rust: https://github.com/sidkshatriya/me/blob/master/007-My-Thoughts-on-OCaml-vs-Haskell-Rust-2023.md
Gotchas
Misc
Features
Composition (.) and dollar sign ($) in std library
http://stackoverflow.com/questions/940382/haskell-difference-between-dot-and-dollar-sign
Tutorials
Haskell tutorials: foldr, foldl, foldl'
Books
expressive typechecking
People in Haskell often claim that the expressiveness of Haskell's type system allows them to use it to represent safety constraints that other languages cannot represent. (ppl using eg ML or F# often make similar claims).
I know a bit of Haskell, but not enough to fully explain or assess these claims.
Some claims i've seen along these lines:
Typechecking impurity
One thing that Haskell does is put monads into the type signature. This allows you to typecheck impurity (side-effects and non-determinism), where by 'impurity' i mean a situation where a function either reads state other than through its input parameters (for example, reading a line of input from the user, or getting a random number from a random number generator, or using a global variable), or alters state other than through its return value (for example, printing out a line of output, writing to a logfile, getting a random number from a random number generator, or changing a global variable). In essence, you can look at impurity as out-of-band reading-from or writing-to global state. Haskell allows you to divide this 'global state' into named pieces, and then to characterize in the type signature exactly which pieces of state each function is allowed to access (and prohibiting any impurity which is not explicitly called out in the type signature). For example:
http://learnyouahaskell.com/for-a-few-monads-more#state
This is said to make programs less error-prone by making it absolutely clear to someone reading (or debugging) a piece of code all the ways in which one function could possibly affect the rest of the program.
Tooling
Internals and implementations
Core data structures: todo
Number representations
Integers
Floating points
note: "what looks like a floating point literal is actually a rational number" -- http://augustss.blogspot.com/2007/04/overloading-haskell-numbers-part-3.html
todo
array representation
variable-length lists: todo
multidimensional arrays: todo
limits on sizes of the above
string representation
Representation of structures with fields
todos
here's a long excerpt from an email i wrote to a friend:
I'd say people mean at least three different things when they talk about functional languages. Sorry if the following is stuff you already know.
First, functional languages are '(pure function)-centric'. that is, one oriented around the mathematical concept of pure functions, which is a fancy way of saying that functions in this language just behave like math functions; they are given an input and they deterministically calculate an output, and they don't otherwise rely upon or affect the state of the caller, or interact with the user (e.g. they have no side effects). Another word for this is 'referential transparency', which is when you can substitute any function call with the value returned from the function without changing semantics. Another term used for this is 'purely functional code'. Purely functional code is 'stateless' in the sense that no external state is being read or written to; all you need to know to determine what a pure function will return is the values you pass in to it as function parameters; and all that a pure function does is give you a return value. Languages that encourage pure functions/referential transparency also tend to encourage immutable data, because mutating a variable is a side-effect.
Second, 'functional programming language' is also sometimes used to mean a '(first class function)-centric programming language', that is, a language in which functions are first class objects. Functions which operate on functions are called higher-order functions, and such a language is often oriented around expressive higher-order functions, often including partial functions. Some languages with higher-order functions have currying, which may be considered 'more functional' because it eliminates the distinction between higher-order functions and multiargument fns.
F# seems to meet all parts of both definitions above. The different uses are probably because functional languages are historically based on lambda calculus, as opposed to Turing machines, and lambda calculus satisfies both of those.
The third definition is archaic and is no longer used (but if you're curious, John Backus created some languages (FP, FL) that he called 'function-level languages', which appears to mean restricting oneself to programs generated by combining a set of primitive functions in a point-free style (I think this is slightly more restrictive than modern functional languages but i don't really understand so i'm not sure); i think those languages are dead but the later Iverson array languages, such as K, are successors).
The main advantage of functional languages comes from the emphasis on pure functions/referential transparency. It is claimed that many bugs come from overuse of side-effects because the programmer has to think about how state evolves as the program executes. It is claimed that because side-effects cause 'hidden state' (eg you can't tell just from looking at a function's signature which external state it reads from or writes to), there is easily-forgotten coupling between different functions.
For example, if function f(x) has the side effect of mutating a global variable, and if the output of function g() depends upon that variables, then in the code "a = f(x); b = g(y)", the value assigned to 'b' depends on what f(x) did, even though you can't see this dependency by looking at the function calls to f() and g() (just looking at the calls, you might assume that the value of 'b' will have nothing to do with f()). In this case it's easy to remember that f() might influence g() because they are right next to each other, but you can imagine that in a large program written by a large team, f() could have been called from a very distant part of the code, making it easier to forget.
You might get the idea that functional programming languages are completely purely functional, but this is not correct; taking input from a user, or printing output to the screen, are 'impure' operations (the first reads from state, and the second writes to state), yet all serious functional programming languages support these operations. The difference is just that functional programming languages encourage purely functional code where possible. As a guy once said, "Purely Functional is the right default" ( https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/sweeny.pdf ).
Another way to put it is that functional languages attempt to make state explicit rather than hidden, and to provide various mechanisms for the programmer to 'control' (as in, to limit) state.
This is one of the practical uses of Haskell's infamous monads. In Haskell, a function that reads from or writes to state must say as much in its type signature. State can be divided up into different regions (or 'monadic values'), and function type signatures can indicate which region of state they access. The advantage is not only making state explicit, but also enabling the static type checker to verify that there isn't any undeclared access to state.
It is claimed that this is good for concurrent programming, because it is claimed that a lot of concurrency bugs are from the programmer forgetting or getting confused about state. In fact, if there is no state at all, that is, if everything is purely functional, then i believe the program can be parallelized while guaranteeing correctness. See https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/sweeny.pdf for a little about this (and a lot about many other things).
Some people also say that maybe the compiler can auto-parallelize purely functional code, because of the above guarantee. However, because forking and merging and communicating between threads is expensive, if you parallelize short or densely communicating operations it can actually be slower than serial. i think the reality is that, the today's compilers can't guess which operations will experience a net gain in speed by parallelizing and which won't. So the programmer still has to get involved.
I hear that F# allows undeclared side effects. Which means that you'd lose the benefits of this static typing discipline.
Haskell actually goes a little bit further than this. One way of thinking of it is that Haskell programs are written as if they are actually, completely, purely functional; stateful interactions are accomplished by having functions return 'actions' (my term, they would say 'values in the IO monad') which are data values that represent a stateful interaction. These 'actions' are in essence impure computer programs. The main() function of Haskell programs builds up, in a completely pure manner, a very complicated 'action', and returns it. This action represents the actual program which is executed. (however, due to lazy execution, this action is actually not pre-built, but rather built 'on-demand'). I found that to be a bit backwards and confusing, but good for learning about purely functional programming.
Another thing Haskell offers that F# may or may not is (pervasive) laziness. Lazy evaluation means that the computer doesn't evaluate a function when it is called, but rather, only when it's return value is needed. Sections 4 and 5 of this paper give detailed examples where laziness is useful: https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf . To summarize section 5, imagine you are writing a chess-playing program. You're going to make the program lookahead and consider some possible moves in the tree of possibilities. Mathematically, the entire tree of possibilities is a well-defined object, but but you aren't going to enumerate the whole tree because that would take too much time and memory. So you need to have a 'search strategy' to decide which possibilities to consider. The way you would ordinarily write this program (without laziness), the search strategy code would be intertwined with the code defining the tree in a mathematical sense. With laziness, you can define the whole tree upfront the same way you do in math, but the tree will not be materialized at the time of its definition. Then you can keep your search strategy code separate, just passing your search strategy as in input the (unmaterialized) return value of the tree-construction code.
Laziness also lets you simply deal with infinite structures by defining them the way you would in math, secure in the knowledge that the runtime won't try to actually compute all of them. Eg the fibonacchi sequence can be defined:
fib 0 = 0 fib 1 = 1 fib n = fib (n-1) + fib (n-2)
but it could also be defined:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
without laziness, 'tail fibs' would cause a problem since fibs is an infinite sequence.
There are various languages which offer the ability to create lazy lists, but without laziness being pervasive, you are afraid to pass your large lazy list to arbitrary library functions from libraries whose source code you haven't read, because that library might do something which would materialize your big (or infinite) lazy list. In Haskell that would be considered a bug, so it's less likely that a library will do that to you.
Laziness, at least the way Haskell does it, has some big disadvantages, too, though. It becomes harder to reason about memory performance. And, at least initially, you have to get into the habit of not unintentionally materializing any lazy lists you are passed, which means learning a lot of little patterns.
Haskell also offers some other things that i dunno if F# does or doesn't have:
- typeclasses. These are kinda like Java interfaces. They let you do ad-hoc polymorphism
- generic polymorphism
- besides the side-effect control mentioned above, monads fill a metaprogramming function. Eg some stuff like LINQ, continuations, etc, can be metaprogrammed with this.
- i've heard that Haskell's syntax is a little more terse than F#. However, i also find Haskell's syntax a bit unreadable (due to custom operator precedence, and also an overly-powerful type inference system).
- i get the sense that Haskell's type system may be more expressive than F# in other ways, but i'm not sure.
Here's what some other ppl think. I havent read these:
People claim that the expressive type system in Haskell lets many bugs be avoided statically by "making illegal states unrepresentable":
Variants and variant implementations
GHC
The most popular Haskell implementation.
Eta
http://eta-lang.org/
"Eta is a dialect of Haskell that aims to bring the benefits of Haskell to the JVM, while supporting the vast majority of GHC extensions so that packages on Hackage can be used with little modification."
Gofer
http://web.cecs.pdx.edu/~mpj/goferarc/index.html
"Gofer (GOod For Equational Reasoning) is a subset of Haskell 1.2. Very small, very fast, but no GC." [53]
Mu
A strict Haskell variant at Standard Chartered
https://icfp21.sigplan.org/details/hiw-2021-papers/14/Haskell-reinterpreted-large-scale-real-world-experience-with-the-Mu-compiler-in-Fin
Misc Links