7 ms·
I've talked to a lot of people using Haskell in production over the years, and one of the things I've consistently noticed is a disconnect between the Haskell f
by kerkeslager 4y ago
I've talked to a lot of people using Haskell in production over the years, and one of the things I've consistently noticed is a disconnect between the Haskell features that are often touted, and the Haskell features that people use in production.
This isn't always a quiet omission, like "we didn't end up using this" either. The best example is that a lot of people end up doing some significant work to disable lazy evaluation. Simon Peyton Jones is reported to have said that lazy evaluation has been kept "to keep us honest about side effects", i.e., that size effects become immediately painful with pervasive lazy evaluation. But in a production system, functional purity (no side-effects) is a means to an end, and lazy evaluation comes at a pretty extreme performance cost. Not everyone disables lazy evaluation in production Haskell, because not everyone thinks they need to for performance, but I've definitely heard about it enough times that it seems like a relevant pattern.
Production users of Haskell do seem to be happy about types, years into projects, so I'd take away that types are a benefit, but I do wonder if one might get those same benefits in another language without the problems which seem to crop up with Haskell, such as laziness causing performance issues, monadic complexity, and difficulty hiring.
- kqr 4y agoSmall distinction that does not detract from your excellent point: the question is not about laziness vs. strictness since any reasonably large program uses both extensively. Rather, it is about the properties of laziness or strictness as a default evaluation strategy.
- yodsanklai 4y agoOCaml may be seen as more pragmatic solution.
- thesz 4y agoIt cannot. Arbitrary side effects allowed by strict-by-default language without types constraining these ruin easeness of software development provided by Haskell. When your Haskell program works and walks, add bangs and it will run. Lately I heard from my colleagues it will run even without bangs as ghc has improved.
- nh2 4y agoI find that too: Purity is Haskell's fundamental killer feature. Other languages now also have quite sophisticated type systems, some of which can do specific checks beyond Haskell in certain areas (Rust's control flow analysis, TypeScript's ways to do "gradual" typing of JS), while Haskell can do more in general. But only in Haskell can I look at a function's type and know it doesn't do any IO. In development and production, this allows you to immediately skip over large amounts of code when debugging issues. Pure functions are a key reason why Haskell is good at correctness and maintainability.
- nh2 4y ago(I say "only in Haskell" disregarding some more sophisticated or specific languages such as Agda, Idris, Futhark, etc. but those are a lot more niche.)
- logicchains 4y agoNim and D both have this feature. Among more popular languages, C++20 constexpr comes close: if you see a constexpr function, you know it's a "pure" function. Although there are still some things that can't be done in constexpr functions, the list is getting smaller and smaller with every new version.
- tome 4y agoEffect tagging in first order, monomorphic code is easy. Doing it in the higher order and polymorphic case is the tricky bit. Do Nim and D support that?
- thesz 4y agoconstexpr reduces general side effects. Can we reduce specific side effects? For one very important example, consider software transactional memory. It has general side effects in the implementation behind the scenes, but inside transaction code they are not allowed. What will C++ solution look like? stmconstexpr?
- 4y ago
- thesz 4y agoIf you disable lazy evaluation, you would not be able to write arbitrary "where" sections, with definitions ordered as you want them, including tying-the-knot definitions, which are not exceptionally rare. So no one disables lazy evaluation. It is strict evaluation that is selectively enabled. I also have to say that focusing on the types in the compiler development was the right thing, but what was not right is not focusing on the automatic analysis and removal of thunk creation for lazy evaluation. There is a project now to implement GRIN (graph reductin intermediate notation) [1] which supports such analysis and GRIN is early 90-th. [1] https://grin-compiler.github.io/ https://grin-compiler.github.io/ GRIN also automatically gives you many benefits of supercompilation. We could have a much better Haskell code now if ghc decided to support several backends back then, not only STG/C--.
- kerkeslager 4y ago> So no one disables lazy evaluation. That's objectively incorrect.
- deleted 4y ago[deleted]
- scns 4y ago> It is strict evaluation that is selectively enabled. Worth fighting over it? Zero snark intended.
- kerkeslager 4y agoProbably not!
- foldr 4y ago>So no one disables lazy evaluation. It is strict evaluation that is selectively enabled. True of vanilla Haskell, but from the article: > So we ended up with Mu, a whole-program compiler to a bytecode representation with a small runtime interpreter... Mu has a strict runtime, which makes program performance easier to analyse and predict – but prevents us from just copy-pasting some Haskell libraries that rely crucially on lazy evaluation. If S&C is to count as a Haskell success story (which it's often touted as), then I think it's fair to point out that it's a codebase where strict evaluation is used throughout.
- yodsanklai 4y agoRegarding hiring difficulty, some say it's a feature rather than a bug. There are always good candidates. And good engineers can learn and be proficient in any language. Haskell is certainly not harder than C++. I see some issues though. Some niche languages enthusiasts can be very picky and reluctant to work on other languages, and would pick Haskell when a simple python script would be more appropriate. People from Academia interested in Haskell may be good in PL theory, and writing compilers or static analysers, but may lack in other fields such as system programming, which incidentally is harder in Haskell too. So you also need to find devops, system guys and so on who can also be productive in Haskell, and those are more rare.
- surement 4y ago> Some niche languages enthusiasts can be very picky and reluctant to work on other languages, and would pick Haskell when a simple python script would be more appropriate. in my experience, these people also refuse to learn about any software engineering best practices as they are "for OO languages" and "not needed" in haskell
- rebeccaskinner 4y agoHiring doesn’t really seem to be a problem for Haskell teams in my experience. Onboarding can be a little slower, but there are a lot of developers out there who want to work with Haskell. If anything, I would consider Haskell a hiring advantage.
- nh2 4y agoHiring for Haskell has never been a problem anywhere I've seen. I've worked profesisonally in Haskell for 9 years now. When working as a consultancy, whenever we hired for ourselves or our clients, we got 5x more applicants than we were looking for. Of those, around 80% got a hire recommendation (unfortunately we couldn't hire them all). When hiring 1 role for our startup, we got 40 good applicants immediately, with a single post on Reddit. In all cases we had the luxury of picking the best-fitting among many excellent engineers. Maybe you'll face issues if you want to hire 100 people on the spot. But most companies don't have that problem.
- dllthomas 4y agoIf you want to hire five developers, Haskell makes that easier. If you want to hire five hundred developers, Haskell probably makes that harder, although it's not something I have experience with.
- tome 4y agoA few minor corrections > Simon Peyton Jones is reported to have said that lazy evaluation has been kept "to keep us honest about side effects" Rather, one of the main benefits of laziness was to force purity and thereby to instigate the development of typed effects. By contrast, the reason that laziness has been "kept" is simply that switching to strictness now would break (almost) all Haskell programs! > lazy evaluation comes at a pretty extreme performance cost Not really. Haskell's implementation of laziness is very high performance and the compiler can easily determine when creating thunks is not necessary. The reason that lazy evaluation leads to performance problems is that programmers don't know how to use it correctly. Whether that's the fault of the language or the programmers is up for debate.
- logicchains 4y ago>The reason that lazy evaluation leads to performance problems is that programmers don't know how to use it correctly It's because it's objectively harder to reason about performance with lazy evaluation as performance becomes non-local. You can no longer reason about a function's performance just by looking at its body, instead you have to know the nature of the inputs passed to it (whether they're thunks or materialised values). Each single uses of lazy evaluation also imposes a latency overhead as the thunk/value must be behind a pointer. Yes the compiler can remove unnecessary thunks, but that's "unnecessary" in the sense of "unnecessary given the semantics of the program", not "unnecessary given the problem the program's trying to solve". I.e. lazy by default will generally result in someone writing code that's lazier than necessary unless they're very careful with annotating to the compiler everywhere laziness isn't needed.
- deleted 4y ago[deleted]
- slacknatcher12 4y ago> It's because it's objectively harder to reason about performance with lazy evaluation as performance becomes non-local. This is off-repeated, but has two interpretations. If you mean performance as in number of steps needed to reduce an expression, lazy evaluation is superior to strict evaluation. If you mean the indirection needed to access a thunk, well someone -- either the producer (strict) or the consumer (lazy) -- had to do that anyways. If GHC can determine that the demand for the value is strict, it won't generate the thunk in the first place. Instead it will provide a specialize calling convention via the worker/wrapper transform, which is standard. On the un-optimized case we still have dynamic pointer tagging (which is a transgression on the T of the STG machine) which serves as a tag to check whether we are dealing with a value or a thunk quickly. So if by non local performance you mean the indirection, modern GHC shows that is not true. If you means that space leaks are a non-local property and they affect performance, well you are sort that right. But as with all programming, we have defensive patterns against that. There are 2 types of space leaks: liveness leaks and strictness leaks. Only the first one are a non-local property, ie the appear as a consequence of the composition of different code pieces. But given the default purity of Haskell, those can only appear on long lived data references with are syntactically marked by: - IORef, MVars, TVars - get/put pairs over a state environment So what you do is to specify in the types that values stored on those environments are to be evaluated before being stored, so references don't leak. I speak about this on this https://epicandmonicisnotiso.blogspot.com/2023/04/how-to-avoid-correctness-space-leaks-on.html https://epicandmonicisnotiso.blogspot.com/2023/04/how-to-avo... and how to specify the a specific evaluation level on the types at here https://epicandmonicisnotiso.blogspot.com/2023/04/presenting-data-forced-alternative-to.html https://epicandmonicisnotiso.blogspot.com/2023/04/presenting... although that library has changed a lot to enforce the invariants.
- time_to_smile 4y agoPersonally I think this is a strength of Haskell that many people don't really recognize and appreciate. It's a language the provides a lot of flexibility. If you want it to be a pure research language, it's happy to do that. If you need to make some sacrifices in regards to purity so you can get stuff into production, you can do that too. As someone with a lot of time spent experimenting with programming languages, I can't think of any that can be so excellent from a research/experimentation perspective that also share the real production usage that Haskell sees. Take for example Racket, an amazing and also extremely flexible research language. It's probably easier to get started with than Haskell, but has never seen the real world usage that I've seen from Haskell.
- scns 4y ago> Simon Peyton Jones is reported to have said that lazy evaluation has been kept "to keep us honest about side effects", i.e., that size effects become immediately painful with pervasive lazy evaluation. IIRC he wrote or said that lazy evaluation, was a mistake in hindsight.
- nequo 4y agoDo you have a source for that? From what I recall, SPJ says that the grass is always greener on the other side. So if Haskell was strict by default, some of us would be pining for a lazy version of it.
- scns 4y agoSadly no. I think it may have been the answer in an interview to what he would have done differently with all the hindsight experience he now has.
- hgsgm 4y agoIt's here: https://news.ycombinator.com/item?id=1924061 https://news.ycombinator.com/item?id=1924061
- hgsgm 4y agoHere: https://news.ycombinator.com/item?id=1924061 https://news.ycombinator.com/item?id=1924061
- aranchelk 4y agoSubtle correction: as far as I’m aware if you were to disable lazy evaluation (which you can now do via compiler pragma) on any Haskell program of significant complexity it would blow up. Common practice is to force evaluation of critical sections of code. That’s something the compiler can sometimes do for you. Laziness in Haskell is a great way to write declarative code, you really are describing a concept rather that telling the computer what to do. That can lead to very clear and orderly source code. Having said that, the drawbacks are significant, performance, memory usage, and debugging can all be a real pain. The last one sucks because it affects development of all kinds of programs, not just performance critical (and challenged) ones. Shout out for PureScript, which is essentially a strictly evaluated Haskell that runs on the browser, servers, and PaaS. Difficulty hiring on a small scale in my experience is complete BS, there are still many passionate users looking for professional gigs. Once that pool is exhausted, I think a company should probably look outside the language for people with some type of FP experience and expect language-specific training as part of onboarding, whether that’s worth the time and cost is entirely settled for me but certainly a topic for debate.
- ParetoOptimal 4y ago> Subtle correction: as far as I’m aware if you were to disable lazy evaluation (which you can now do via compiler pragma) on any Haskell program of significant complexity it would blow up. Common practice is to force evaluation of critical sections of code. That’s something the compiler can sometimes do for you. Anecdotally I can confirm this is true. > Laziness in Haskell is a great way to write declarative code, you really are describing a concept rather that telling the computer what to do. That can lead to very clear and orderly source code. 100% agree. > Having said that, the drawbacks are significant, performance, memory usage, and debugging can all be a real pain. The last one sucks because it affects development of all kinds of programs, not just performance critical (and challenged) ones. I think this largely depends on domain. Then, it's not clear if the best tradeoff is to go fully strict or develop better intuition for lazy evaluation in many of those cases. > Difficulty hiring on a small scale in my experience is complete BS I have a theory this meme is repeated by people who don't know where to find the many functional programming candidates.
- ParetoOptimal 4y ago> But in a production system, functional purity (no side-effects) is a means to an end, and lazy evaluation comes at a pretty extreme performance cost. In theory yes, in practice... not in my experience. Or at least, things are almost always fast enough in web backends. > Not everyone disables lazy evaluation in production Haskell, because not everyone thinks they need to for performance, but I've definitely heard about it enough times that it seems like a relevant pattern. Most people don't disable lazy evaluation in the way I think you mean it, but they use either BangPatterns selectively or StrictData. Maybe there's widespread usage of `-XStrict` as well I don't know about. Anecdotally I can say that trying to enable `-XStrict` in production caused performance issues that prevented us from trialing it further. Laziness is frequently talked about as a negative, but it has big advantages in composition and code ordering like another reply talked about with regards to where bindings to enable truly top-down program design.
- whateveracct 4y ago> Anecdotally I can say that trying to enable `-XStrict` in production caused performance issues that prevented us from trialing it further. hah this is in many ways indirect proof that laziness-by-default is improving performance. It may even be that a rote translation to strict (which `-XStrict` does) is causing _time_ leaks, which are actually pervasive in production strict-by-default code.
- ParetoOptimal 4y ago> hah this is in many ways indirect proof that laziness-by-default is improving performance. I believe this is the case, but the times it saves you are invisble. The occurences where it bites you though are very visible and made out to be a much bigger deal than they usually are.
- ParetoOptimal 4y ago> monadic complexity In production Haskell I've seen it's typical you only need to understand how to use the common Maybe, Either, and IO monads. Maybe... sometimes... you need to create your own. I actually think it's a bit of an anti-pattern, but this anti-pattern leads to a reality where the monadic complexity you alledge does not exist.
- jsbg 4y agoIn production Haskell you need to combine monads using e.g. monad transformers and this is significantly more complicated than just understanding how bind works. It also requires a lot of boilerplate code and any errors there are not obvious to track down.
- consilient 4y agoWriting your own monad transformers is more complicated than understanding bind. Using standard ones isn't: swap out `State s a` for `MonadState s m => m a` (or whatever the relevant effects are) and put `lift` where the compiler tells you to, and you're 90% of the way there. The other 10% is stacking the appropriate sequence of `FooBarT (BarBazT ... )` at the entry point to your program, which is admittedly pretty tedious.
- ParetoOptimal 4y ago> In production Haskell you need to combine monads using e.g. monad transformers In some workplaces, yes. However it's pretty tenable to use https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_cake.html https://www.parsonsmatt.org/2018/03/22/three_layer_haskell_c... with no or few additional monads. Alternatively, you can have just a few people that understand how to compose Monads and many others writing code within them. I'm not sure if I think it's a best practice by any means, but it's a pattern I've seen that avoids having to include monad transformers in onboarding.
- ParetoOptimal 4y agoAlso a much simpler alternative in my opinion to monad transformers is effectful: https://github.com/haskell-effectful/effectful https://github.com/haskell-effectful/effectful Here's a talk on it: https://www.youtube.com/watch?v=BUoYKBLOOrE https://www.youtube.com/watch?v=BUoYKBLOOrE
- whateveracct 4y agoPeople take advantage of laziness without even realizing it in production all the time. In fact, people often get free performance gains due to laziness all the time!
- hardwaresofton 4y ago> Production users of Haskell do seem to be happy about types, years into projects, so I'd take away that types are a benefit, but I do wonder if one might get those same benefits in another language without the problems which seem to crop up with Haskell, such as laziness causing performance issues, monadic complexity, and difficulty hiring. Rust. Rust is that language. Rust's type system is good enough and close enough to Haskell without the footguns of other options (Java, C++, etc), with new paradigms that Haskell hasn't yet fully adopted (borrowing, etc) and consistency in build tooling, etc. I hate to say it, but it's incredibly hard to suggest people run Haskell in production when Rust exists and you basically trade the difficulty of learning monads + mtl + ... for the difficulty of learning to live with the borrow checker.
- ParetoOptimal 4y agoRust is good, but writing the experience writing Rust isn't like the experience writing Haskell. Here is a good post on it: https://serokell.io/blog/rust-vs-haskell https://serokell.io/blog/rust-vs-haskell
- icrbow 4y ago> Rust's type system is good enough Good enough for what? > close enough to Haskell Close enough for what? > with new paradigms that Haskell hasn't yet fully adopted (borrowing, etc) Honestly, I don't want to think about borrowing. For my gamedev/rendering tasks I have resource regions (as a library) and trust the runtime to do its thing.
- grumpyprole 4y ago> I do wonder if one might get those same benefits in another language without the problems which seem to crop up with Haskell Not until other languages raise their game. Nothing compares to Haskell for productivity and I've tried many different languages professionally over the years (including Haskell). But yes, reasoning about what happens operationally is the big problem.
- ParetoOptimal 4y ago> Not until other languages raise their game. Nothing compares to Haskell for productivity and I've tried many different languages professionally over the years (including Haskell). 100% agree. > But yes, reasoning about what happens operationally is the big problem. What types of issues do you experience?
- grumpyprole 4y ago> What types of issues do you experience? In GHC Haskell, it can be difficult to predict the lifetime of heap objects and control when objects are not shared (optimisations may or not lift terms out of a local context).
- garrisonj 4y agoReally? I can’t imagine people turning off the lazy evaluation. It’s a deep part of the language. You can force things to evaluate strictly, but that’s not disabling it.
- brundolf 4y ago> lazy evaluation comes at a pretty extreme performance cost This is surprising to me. I understand it making code harder to debug because timing/ordering is less predictable, but I would expect lazy evaluation to help with performance if anything ("the code doesnt rely on a specific 'when', so the compiler/runtime are free to re-arrange stuff for optimization") What am I missing?